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)
87 anchor_row_ = rows().end();
88 anchor_row_offset_ = 0;
90 current_font = getFont(ownerParagraphs().begin(), 0);
92 redoParagraphs(ownerParagraphs().begin(), ownerParagraphs().end());
94 setCursorIntern(rowlist_.begin()->par(), 0);
95 selection.cursor = cursor;
101 // Gets the fully instantiated font at a given position in a paragraph
102 // Basically the same routine as Paragraph::getFont() in paragraph.C.
103 // The difference is that this one is used for displaying, and thus we
104 // are allowed to make cosmetic improvements. For instance make footnotes
106 // If position is -1, we get the layout font of the paragraph.
107 // If position is -2, we get the font of the manual label of the paragraph.
108 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
112 LyXLayout_ptr const & layout = pit->layout();
114 BufferParams const & params = bv()->buffer()->params;
116 // We specialize the 95% common case:
117 if (!pit->getDepth()) {
118 if (layout->labeltype == LABEL_MANUAL
119 && pos < pit->beginningOfBody()) {
121 LyXFont f = pit->getFontSettings(params, pos);
123 pit->inInset()->getDrawFont(f);
124 return f.realize(layout->reslabelfont);
126 LyXFont f = pit->getFontSettings(params, pos);
128 pit->inInset()->getDrawFont(f);
129 return f.realize(layout->resfont);
133 // The uncommon case need not be optimized as much
137 if (pos < pit->beginningOfBody()) {
139 layoutfont = layout->labelfont;
142 layoutfont = layout->font;
145 LyXFont tmpfont = pit->getFontSettings(params, pos);
146 tmpfont.realize(layoutfont);
149 pit->inInset()->getDrawFont(tmpfont);
151 // Realize with the fonts of lesser depth.
152 tmpfont.realize(outerFont(pit, ownerParagraphs()));
153 tmpfont.realize(defaultfont_);
159 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
161 LyXLayout_ptr const & layout = pit->layout();
163 if (!pit->getDepth())
164 return layout->resfont;
166 LyXFont font = layout->font;
167 // Realize with the fonts of lesser depth.
168 font.realize(outerFont(pit, ownerParagraphs()));
169 font.realize(defaultfont_);
175 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
177 LyXLayout_ptr const & layout = pit->layout();
179 if (!pit->getDepth())
180 return layout->reslabelfont;
182 LyXFont font = layout->labelfont;
183 // Realize with the fonts of lesser depth.
184 font.realize(outerFont(pit, ownerParagraphs()));
185 font.realize(defaultfont_);
191 void LyXText::setCharFont(ParagraphList::iterator pit,
192 pos_type pos, LyXFont const & fnt,
195 BufferParams const & params = bv()->buffer()->params;
196 LyXFont font = getFont(pit, pos);
197 font.update(fnt, params.language, toggleall);
198 // Let the insets convert their font
199 if (pit->isInset(pos)) {
200 InsetOld * inset = pit->getInset(pos);
201 if (isEditableInset(inset)) {
202 static_cast<UpdatableInset *>(inset)
203 ->setFont(bv(), fnt, toggleall, true);
207 // Plug through to version below:
208 setCharFont(pit, pos, font);
212 void LyXText::setCharFont(
213 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
216 LyXLayout_ptr const & layout = pit->layout();
218 // Get concrete layout font to reduce against
221 if (pos < pit->beginningOfBody())
222 layoutfont = layout->labelfont;
224 layoutfont = layout->font;
226 // Realize against environment font information
227 if (pit->getDepth()) {
228 ParagraphList::iterator tp = pit;
229 while (!layoutfont.resolved() &&
230 tp != ownerParagraphs().end() &&
232 tp = outerHook(tp, ownerParagraphs());
233 if (tp != ownerParagraphs().end())
234 layoutfont.realize(tp->layout()->font);
238 layoutfont.realize(defaultfont_);
240 // Now, reduce font against full layout font
241 font.reduce(layoutfont);
243 pit->setFont(pos, font);
247 // removes the row and reset the touched counters
248 void LyXText::removeRow(RowList::iterator rit)
250 if (anchor_row_ == rit) {
251 if (rit != rows().begin()) {
252 anchor_row_ = boost::prior(rit);
253 anchor_row_offset_ += anchor_row_->height();
255 anchor_row_ = boost::next(rit);
256 anchor_row_offset_ -= rit->height();
260 // the text becomes smaller
261 height -= rit->height();
267 // remove all following rows of the paragraph of the specified row.
268 void LyXText::removeParagraph(RowList::iterator rit)
270 ParagraphList::iterator tmppit = rit->par();
273 while (rit != rows().end() && rit->par() == tmppit) {
274 RowList::iterator tmprit = boost::next(rit);
281 void LyXText::insertParagraph(ParagraphList::iterator pit,
282 RowList::iterator rit)
284 // insert a new row, starting at position 0
285 rit = rowlist_.insert(rit, Row(pit, 0));
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);
333 /* used in setlayout */
334 // Asger is not sure we want to do this...
335 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
338 LyXLayout_ptr const & layout = par.layout();
339 pos_type const psize = par.size();
342 for (pos_type pos = 0; pos < psize; ++pos) {
343 if (pos < par.beginningOfBody())
344 layoutfont = layout->labelfont;
346 layoutfont = layout->font;
348 LyXFont tmpfont = par.getFontSettings(params, pos);
349 tmpfont.reduce(layoutfont);
350 par.setFont(pos, tmpfont);
355 ParagraphList::iterator
356 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
357 LyXCursor & send_cur,
358 string const & layout)
360 ParagraphList::iterator endpit = boost::next(send_cur.par());
361 ParagraphList::iterator undoendpit = endpit;
362 ParagraphList::iterator pars_end = ownerParagraphs().end();
364 if (endpit != pars_end && endpit->getDepth()) {
365 while (endpit != pars_end && endpit->getDepth()) {
369 } else if (endpit != pars_end) {
370 // because of parindents etc.
374 recordUndo(bv(), Undo::ATOMIC, sstart_cur.par(), boost::prior(undoendpit));
376 // ok we have a selection. This is always between sstart_cur
377 // and sel_end cursor
379 ParagraphList::iterator pit = sstart_cur.par();
380 ParagraphList::iterator epit = boost::next(send_cur.par());
382 LyXLayout_ptr const & lyxlayout =
383 bv()->buffer()->params.getLyXTextClass()[layout];
386 pit->applyLayout(lyxlayout);
387 makeFontEntriesLayoutSpecific(bv()->buffer()->params, *pit);
388 ParagraphList::iterator fppit = pit;
389 fppit->params().spaceTop(lyxlayout->fill_top ?
390 VSpace(VSpace::VFILL)
391 : VSpace(VSpace::NONE));
392 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
393 VSpace(VSpace::VFILL)
394 : VSpace(VSpace::NONE));
395 if (lyxlayout->margintype == MARGIN_MANUAL)
396 pit->setLabelWidthString(lyxlayout->labelstring());
399 } while (pit != epit);
405 // set layout over selection and make a total rebreak of those paragraphs
406 void LyXText::setLayout(string const & layout)
408 LyXCursor tmpcursor = cursor; // store the current cursor
410 // if there is no selection just set the layout
411 // of the current paragraph
412 if (!selection.set()) {
413 selection.start = cursor; // dummy selection
414 selection.end = cursor;
417 // special handling of new environment insets
418 BufferParams const & params = bv()->buffer()->params;
419 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
420 if (lyxlayout->is_environment) {
421 // move everything in a new environment inset
422 lyxerr << "setting layout " << layout << endl;
423 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
424 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
425 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
426 InsetOld * inset = new InsetEnvironment(params, layout);
427 if (bv()->insertInset(inset)) {
429 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
436 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
437 selection.end, layout);
438 redoParagraphs(selection.start.par(), endpit);
440 // we have to reset the selection, because the
441 // geometry could have changed
442 setCursor(selection.start.par(), selection.start.pos(), false);
443 selection.cursor = cursor;
444 setCursor(selection.end.par(), selection.end.pos(), false);
448 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
452 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
454 ParagraphList::iterator pit(cursor.par());
455 ParagraphList::iterator end(cursor.par());
456 ParagraphList::iterator start = pit;
458 if (selection.set()) {
459 pit = selection.start.par();
460 end = selection.end.par();
464 ParagraphList::iterator pastend = boost::next(end);
467 recordUndo(bv(), Undo::ATOMIC, start, end);
469 bool changed = false;
471 int prev_after_depth = 0;
472 #warning parlist ... could be nicer ?
473 if (start != ownerParagraphs().begin()) {
474 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
478 int const depth = pit->params().depth();
479 if (type == bv_funcs::INC_DEPTH) {
480 if (depth < prev_after_depth
481 && pit->layout()->labeltype != LABEL_BIBLIO) {
484 pit->params().depth(depth + 1);
491 pit->params().depth(depth - 1);
494 prev_after_depth = pit->getMaxDepthAfter();
507 redoParagraphs(start, pastend);
509 // We need to actually move the text->cursor. I don't
510 // understand why ...
511 LyXCursor tmpcursor = cursor;
513 // we have to reset the visual selection because the
514 // geometry could have changed
515 if (selection.set()) {
516 setCursor(selection.start.par(), selection.start.pos());
517 selection.cursor = cursor;
518 setCursor(selection.end.par(), selection.end.pos());
521 // this handles the counter labels, and also fixes up
522 // depth values for follow-on (child) paragraphs
526 setCursor(tmpcursor.par(), tmpcursor.pos());
532 // set font over selection and make a total rebreak of those paragraphs
533 void LyXText::setFont(LyXFont const & font, bool toggleall)
535 // if there is no selection just set the current_font
536 if (!selection.set()) {
537 // Determine basis font
539 if (cursor.pos() < cursor.par()->beginningOfBody()) {
540 layoutfont = getLabelFont(cursor.par());
542 layoutfont = getLayoutFont(cursor.par());
544 // Update current font
545 real_current_font.update(font,
546 bv()->buffer()->params.language,
549 // Reduce to implicit settings
550 current_font = real_current_font;
551 current_font.reduce(layoutfont);
552 // And resolve it completely
553 real_current_font.realize(layoutfont);
558 LyXCursor tmpcursor = cursor; // store the current cursor
560 // ok we have a selection. This is always between sel_start_cursor
561 // and sel_end cursor
563 recordUndo(bv(), Undo::ATOMIC, selection.start.par(), selection.end.par());
565 cursor = selection.start;
566 while (cursor.par() != selection.end.par() ||
567 cursor.pos() < selection.end.pos())
569 if (cursor.pos() < cursor.par()->size()) {
570 // an open footnote should behave like a closed one
571 setCharFont(cursor.par(), cursor.pos(),
573 cursor.pos(cursor.pos() + 1);
576 cursor.par(boost::next(cursor.par()));
581 redoParagraph(selection.start.par());
583 // we have to reset the selection, because the
584 // geometry could have changed, but we keep
585 // it for user convenience
586 setCursor(selection.start.par(), selection.start.pos());
587 selection.cursor = cursor;
588 setCursor(selection.end.par(), selection.end.pos());
590 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
591 tmpcursor.boundary());
595 RowList::iterator LyXText::firstRow(ParagraphList::iterator pit)
597 RowList::iterator rit;
598 for (rit = rows().begin(); rit != rows().end(); ++rit)
599 if (rit->par() == pit)
605 // rebreaks all paragraphs between the specified pars
606 // This function is needed after SetLayout and SetFont etc.
607 void LyXText::redoParagraphs(ParagraphList::iterator start,
608 ParagraphList::iterator end)
610 for ( ; start != end; ++start)
611 redoParagraph(start);
615 void LyXText::redoParagraph(ParagraphList::iterator pit)
617 RowList::iterator rit = firstRow(pit);
619 // remove paragraph from rowlist
620 while (rit != rows().end() && rit->par() == pit) {
621 RowList::iterator rit2 = rit++;
625 // reinsert the paragraph
626 // insert a new row, starting at position 0
628 rit = rowlist_.insert(rit, newrow);
630 // and now append the whole paragraph before the new row
631 pos_type const last = rit->par()->size();
635 pos_type z = rowBreakPoint(*rit);
637 RowList::iterator tmprow = rit;
641 Row newrow(rit->par(), z);
642 rit = rowlist_.insert(boost::next(rit), newrow);
647 // Set the dimensions of the row
648 // fixed fill setting now by calling inset->update() in
649 // SingleWidth when needed!
650 tmprow->fill(fill(tmprow, workWidth()));
651 setHeightOfRow(tmprow);
655 setHeightOfRow(rows().begin());
659 void LyXText::fullRebreak()
661 redoParagraphs(ownerParagraphs().begin(), ownerParagraphs().end());
662 setCursorIntern(cursor.par(), cursor.pos());
663 selection.cursor = cursor;
667 void LyXText::metrics(MetricsInfo & mi, Dimension & dim)
669 //lyxerr << "LyXText::metrics: width: " << mi.base.textwidth << endl;
670 //Assert(mi.base.textwidth);
677 anchor_row_ = rows().end();
678 anchor_row_offset_ = 0;
680 ParagraphList::iterator pit = ownerParagraphs().begin();
681 ParagraphList::iterator end = ownerParagraphs().end();
683 for (; pit != end; ++pit) {
684 InsetList::iterator ii = pit->insetlist.begin();
685 InsetList::iterator iend = pit->insetlist.end();
686 for (; ii != iend; ++ii) {
689 #warning FIXME: pos != 0
690 m.base.font = getFont(pit, 0);
691 ii->inset->metrics(m, dim);
695 // insert a new row, starting at position 0
697 RowList::iterator rit = rowlist_.insert(rowlist_.end(), newrow);
699 // and now append the whole paragraph before the new row
700 appendParagraph(rit);
707 //lyxerr << "height 0: " << height << endl;
708 //for (RowList::iterator rit = rows().begin(); rit != rows().end(); ++rit) {
709 // height += rit->height();
711 //lyxerr << "height 1: " << height << endl;
714 dim.asc = rows().begin()->ascent_of_text();
715 dim.des = height - dim.asc;
716 dim.wid = std::max(mi.base.textwidth, int(width));
720 void LyXText::partialRebreak()
722 if (rows().empty()) {
726 breakAgain(rows().begin());
730 // important for the screen
733 // the cursor set functions have a special mechanism. When they
734 // realize, that you left an empty paragraph, they will delete it.
735 // They also delete the corresponding row
737 // need the selection cursor:
738 void LyXText::setSelection()
740 TextCursor::setSelection();
745 void LyXText::clearSelection()
747 TextCursor::clearSelection();
749 // reset this in the bv_owner!
750 if (bv_owner && bv_owner->text)
751 bv_owner->text->xsel_cache.set(false);
755 void LyXText::cursorHome()
757 setCursor(cursor.par(), cursorRow()->pos());
761 void LyXText::cursorEnd()
763 if (cursor.par()->empty())
766 RowList::iterator rit = cursorRow();
767 RowList::iterator next_rit = boost::next(rit);
768 ParagraphList::iterator pit = rit->par();
769 pos_type last_pos = lastPos(*this, rit);
771 if (next_rit == rows().end() || next_rit->par() != pit) {
775 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
780 setCursor(pit, last_pos);
784 void LyXText::cursorTop()
786 setCursor(ownerParagraphs().begin(), 0);
790 void LyXText::cursorBottom()
792 ParagraphList::iterator lastpit =
793 boost::prior(ownerParagraphs().end());
794 setCursor(lastpit, lastpit->size());
798 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
800 // If the mask is completely neutral, tell user
801 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
802 // Could only happen with user style
803 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
807 // Try implicit word selection
808 // If there is a change in the language the implicit word selection
810 LyXCursor resetCursor = cursor;
811 bool implicitSelection = (font.language() == ignore_language
812 && font.number() == LyXFont::IGNORE)
813 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
816 setFont(font, toggleall);
818 // Implicit selections are cleared afterwards
819 //and cursor is set to the original position.
820 if (implicitSelection) {
822 cursor = resetCursor;
823 setCursor(cursor.par(), cursor.pos());
824 selection.cursor = cursor;
829 string LyXText::getStringToIndex()
831 // Try implicit word selection
832 // If there is a change in the language the implicit word selection
834 LyXCursor const reset_cursor = cursor;
835 bool const implicitSelection =
836 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
839 if (!selection.set())
840 bv()->owner()->message(_("Nothing to index!"));
841 else if (selection.start.par() != selection.end.par())
842 bv()->owner()->message(_("Cannot index more than one paragraph!"));
844 idxstring = selectionAsString(bv()->buffer(), false);
846 // Reset cursors to their original position.
847 cursor = reset_cursor;
848 setCursor(cursor.par(), cursor.pos());
849 selection.cursor = cursor;
851 // Clear the implicit selection.
852 if (implicitSelection)
859 // the DTP switches for paragraphs. LyX will store them in the first
860 // physicla paragraph. When a paragraph is broken, the top settings rest,
861 // the bottom settings are given to the new one. So I can make shure,
862 // they do not duplicate themself and you cannnot make dirty things with
865 void LyXText::setParagraph(bool line_top, bool line_bottom,
866 bool pagebreak_top, bool pagebreak_bottom,
867 VSpace const & space_top,
868 VSpace const & space_bottom,
869 Spacing const & spacing,
871 string const & labelwidthstring,
874 LyXCursor tmpcursor = cursor;
875 if (!selection.set()) {
876 selection.start = cursor;
877 selection.end = cursor;
880 // make sure that the depth behind the selection are restored, too
881 ParagraphList::iterator endpit = boost::next(selection.end.par());
882 ParagraphList::iterator undoendpit = endpit;
883 ParagraphList::iterator pars_end = ownerParagraphs().end();
885 if (endpit != pars_end && endpit->getDepth()) {
886 while (endpit != pars_end && endpit->getDepth()) {
890 } else if (endpit != pars_end) {
891 // because of parindents etc.
895 recordUndo(bv(), Undo::ATOMIC, selection.start.par(),
896 boost::prior(undoendpit));
899 ParagraphList::iterator tmppit = selection.end.par();
901 while (tmppit != boost::prior(selection.start.par())) {
902 setCursor(tmppit, 0);
904 ParagraphList::iterator pit = cursor.par();
905 ParagraphParameters & params = pit->params();
907 params.lineTop(line_top);
908 params.lineBottom(line_bottom);
909 params.pagebreakTop(pagebreak_top);
910 params.pagebreakBottom(pagebreak_bottom);
911 params.spaceTop(space_top);
912 params.spaceBottom(space_bottom);
913 params.spacing(spacing);
914 // does the layout allow the new alignment?
915 LyXLayout_ptr const & layout = pit->layout();
917 if (align == LYX_ALIGN_LAYOUT)
918 align = layout->align;
919 if (align & layout->alignpossible) {
920 if (align == layout->align)
921 params.align(LYX_ALIGN_LAYOUT);
925 pit->setLabelWidthString(labelwidthstring);
926 params.noindent(noindent);
927 tmppit = boost::prior(pit);
930 redoParagraphs(selection.start.par(), endpit);
933 setCursor(selection.start.par(), selection.start.pos());
934 selection.cursor = cursor;
935 setCursor(selection.end.par(), selection.end.pos());
937 setCursor(tmpcursor.par(), tmpcursor.pos());
943 // set the counter of a paragraph. This includes the labels
944 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
946 LyXTextClass const & textclass = buf->params.getLyXTextClass();
947 LyXLayout_ptr const & layout = pit->layout();
949 if (pit != ownerParagraphs().begin()) {
951 pit->params().appendix(boost::prior(pit)->params().appendix());
952 if (!pit->params().appendix() &&
953 pit->params().startOfAppendix()) {
954 pit->params().appendix(true);
955 textclass.counters().reset();
957 pit->enumdepth = boost::prior(pit)->enumdepth;
958 pit->itemdepth = boost::prior(pit)->itemdepth;
960 pit->params().appendix(pit->params().startOfAppendix());
965 /* Maybe we have to increment the enumeration depth.
966 * BUT, enumeration in a footnote is considered in isolation from its
967 * surrounding paragraph so don't increment if this is the
968 * first line of the footnote
969 * AND, bibliographies can't have their depth changed ie. they
970 * are always of depth 0
972 if (pit != ownerParagraphs().begin()
973 && boost::prior(pit)->getDepth() < pit->getDepth()
974 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
975 && pit->enumdepth < 3
976 && layout->labeltype != LABEL_BIBLIO) {
980 // Maybe we have to decrement the enumeration depth, see note above
981 if (pit != ownerParagraphs().begin()
982 && boost::prior(pit)->getDepth() > pit->getDepth()
983 && layout->labeltype != LABEL_BIBLIO) {
984 pit->enumdepth = depthHook(pit, ownerParagraphs(),
985 pit->getDepth())->enumdepth;
988 if (!pit->params().labelString().empty()) {
989 pit->params().labelString(string());
992 if (layout->margintype == MARGIN_MANUAL) {
993 if (pit->params().labelWidthString().empty()) {
994 pit->setLabelWidthString(layout->labelstring());
997 pit->setLabelWidthString(string());
1000 // is it a layout that has an automatic label?
1001 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1002 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1006 if (i >= 0 && i <= buf->params.secnumdepth) {
1010 textclass.counters().step(layout->latexname());
1012 // Is there a label? Useful for Chapter layout
1013 if (!pit->params().appendix()) {
1014 s << buf->B_(layout->labelstring());
1016 s << buf->B_(layout->labelstring_appendix());
1019 // Use of an integer is here less than elegant. For now.
1020 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1021 if (!pit->params().appendix()) {
1022 numbertype = "sectioning";
1024 numbertype = "appendix";
1025 if (pit->isRightToLeftPar(buf->params))
1026 langtype = "hebrew";
1032 << textclass.counters()
1033 .numberLabel(layout->latexname(),
1034 numbertype, langtype, head);
1036 pit->params().labelString(STRCONV(s.str()));
1038 // reset enum counters
1039 textclass.counters().reset("enum");
1040 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1041 textclass.counters().reset("enum");
1042 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1044 // Yes I know this is a really, really! bad solution
1046 string enumcounter("enum");
1048 switch (pit->enumdepth) {
1057 enumcounter += "iv";
1060 // not a valid enumdepth...
1064 textclass.counters().step(enumcounter);
1066 s << textclass.counters()
1067 .numberLabel(enumcounter, "enumeration");
1068 pit->params().labelString(STRCONV(s.str()));
1070 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1071 textclass.counters().step("bibitem");
1072 int number = textclass.counters().value("bibitem");
1073 if (pit->bibitem()) {
1074 pit->bibitem()->setCounter(number);
1075 pit->params().labelString(layout->labelstring());
1077 // In biblio should't be following counters but...
1079 string s = buf->B_(layout->labelstring());
1081 // the caption hack:
1082 if (layout->labeltype == LABEL_SENSITIVE) {
1083 ParagraphList::iterator end = ownerParagraphs().end();
1084 ParagraphList::iterator tmppit = pit;
1087 while (tmppit != end && tmppit->inInset()
1088 // the single '=' is intended below
1089 && (in = tmppit->inInset()->owner()))
1091 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
1092 in->lyxCode() == InsetOld::WRAP_CODE) {
1096 tmppit = ownerParagraphs().begin();
1097 for ( ; tmppit != end; ++tmppit)
1098 if (&*tmppit == in->parOwner())
1106 if (in->lyxCode() == InsetOld::FLOAT_CODE)
1107 type = static_cast<InsetFloat*>(in)->params().type;
1108 else if (in->lyxCode() == InsetOld::WRAP_CODE)
1109 type = static_cast<InsetWrap*>(in)->params().type;
1113 Floating const & fl = textclass.floats().getType(type);
1115 textclass.counters().step(fl.type());
1117 // Doesn't work... yet.
1118 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1120 // par->SetLayout(0);
1121 // s = layout->labelstring;
1122 s = _("Senseless: ");
1125 pit->params().labelString(s);
1127 // reset the enumeration counter. They are always reset
1128 // when there is any other layout between
1129 // Just fall-through between the cases so that all
1130 // enum counters deeper than enumdepth is also reset.
1131 switch (pit->enumdepth) {
1133 textclass.counters().reset("enumi");
1135 textclass.counters().reset("enumii");
1137 textclass.counters().reset("enumiii");
1139 textclass.counters().reset("enumiv");
1145 // Updates all counters. Paragraphs with changed label string will be rebroken
1146 void LyXText::updateCounters()
1148 RowList::iterator rowit = rows().begin();
1149 ParagraphList::iterator pit = rowit->par();
1151 // CHECK if this is really needed. (Lgb)
1152 bv()->buffer()->params.getLyXTextClass().counters().reset();
1154 ParagraphList::iterator beg = ownerParagraphs().begin();
1155 ParagraphList::iterator end = ownerParagraphs().end();
1156 for (; pit != end; ++pit) {
1157 while (rowit->par() != pit)
1160 string const oldLabel = pit->params().labelString();
1162 size_t maxdepth = 0;
1164 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1166 if (pit->params().depth() > maxdepth)
1167 pit->params().depth(maxdepth);
1169 // setCounter can potentially change the labelString.
1170 setCounter(bv()->buffer(), pit);
1172 string const & newLabel = pit->params().labelString();
1174 if (oldLabel.empty() && !newLabel.empty()) {
1175 removeParagraph(rowit);
1176 appendParagraph(rowit);
1182 void LyXText::insertInset(InsetOld * inset)
1184 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1186 recordUndo(bv(), Undo::ATOMIC, cursor.par());
1188 cursor.par()->insertInset(cursor.pos(), inset);
1189 // Just to rebreak and refresh correctly.
1190 // The character will not be inserted a second time
1191 insertChar(Paragraph::META_INSET);
1192 // If we enter a highly editable inset the cursor should be to before
1193 // the inset. This couldn't happen before as Undo was not handled inside
1194 // inset now after the Undo LyX tries to call inset->Edit(...) again
1195 // and cannot do this as the cursor is behind the inset and GetInset
1196 // does not return the inset!
1197 if (isHighlyEditableInset(inset)) {
1204 void LyXText::cutSelection(bool doclear, bool realcut)
1206 // Stuff what we got on the clipboard. Even if there is no selection.
1208 // There is a problem with having the stuffing here in that the
1209 // larger the selection the slower LyX will get. This can be
1210 // solved by running the line below only when the selection has
1211 // finished. The solution used currently just works, to make it
1212 // faster we need to be more clever and probably also have more
1213 // calls to stuffClipboard. (Lgb)
1214 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1216 // This doesn't make sense, if there is no selection
1217 if (!selection.set())
1220 // OK, we have a selection. This is always between selection.start
1221 // and selection.end
1223 // make sure that the depth behind the selection are restored, too
1224 ParagraphList::iterator endpit = boost::next(selection.end.par());
1225 ParagraphList::iterator undoendpit = endpit;
1226 ParagraphList::iterator pars_end = ownerParagraphs().end();
1228 if (endpit != pars_end && endpit->getDepth()) {
1229 while (endpit != pars_end && endpit->getDepth()) {
1231 undoendpit = endpit;
1233 } else if (endpit != pars_end) {
1234 // because of parindents etc.
1238 recordUndo(bv(), Undo::DELETE, selection.start.par(),
1239 boost::prior(undoendpit));
1242 endpit = selection.end.par();
1243 int endpos = selection.end.pos();
1245 boost::tie(endpit, endpos) = realcut ?
1246 CutAndPaste::cutSelection(bv()->buffer()->params,
1248 selection.start.par(), endpit,
1249 selection.start.pos(), endpos,
1250 bv()->buffer()->params.textclass,
1252 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1254 selection.start.par(), endpit,
1255 selection.start.pos(), endpos,
1257 // sometimes necessary
1259 selection.start.par()->stripLeadingSpaces();
1261 redoParagraphs(selection.start.par(), boost::next(endpit));
1262 // cutSelection can invalidate the cursor so we need to set
1264 // we prefer the end for when tracking changes
1268 // need a valid cursor. (Lgb)
1271 setCursor(cursor.par(), cursor.pos());
1272 selection.cursor = cursor;
1277 void LyXText::copySelection()
1279 // stuff the selection onto the X clipboard, from an explicit copy request
1280 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1282 // this doesnt make sense, if there is no selection
1283 if (!selection.set())
1286 // ok we have a selection. This is always between selection.start
1287 // and sel_end cursor
1289 // copy behind a space if there is one
1290 while (selection.start.par()->size() > selection.start.pos()
1291 && selection.start.par()->isLineSeparator(selection.start.pos())
1292 && (selection.start.par() != selection.end.par()
1293 || selection.start.pos() < selection.end.pos()))
1294 selection.start.pos(selection.start.pos() + 1);
1296 CutAndPaste::copySelection(selection.start.par(),
1297 selection.end.par(),
1298 selection.start.pos(), selection.end.pos(),
1299 bv()->buffer()->params.textclass);
1303 void LyXText::pasteSelection(size_t sel_index)
1305 // this does not make sense, if there is nothing to paste
1306 if (!CutAndPaste::checkPastePossible())
1309 recordUndo(bv(), Undo::INSERT, cursor.par());
1311 ParagraphList::iterator endpit;
1316 boost::tie(ppp, endpit) =
1317 CutAndPaste::pasteSelection(*bv()->buffer(),
1319 cursor.par(), cursor.pos(),
1320 bv()->buffer()->params.textclass,
1322 bufferErrors(*bv()->buffer(), el);
1323 bv()->showErrorList(_("Paste"));
1325 redoParagraphs(cursor.par(), endpit);
1327 setCursor(cursor.par(), cursor.pos());
1330 selection.cursor = cursor;
1331 setCursor(ppp.first, ppp.second);
1337 void LyXText::setSelectionRange(lyx::pos_type length)
1342 selection.cursor = cursor;
1349 // simple replacing. The font of the first selected character is used
1350 void LyXText::replaceSelectionWithString(string const & str)
1352 recordUndo(bv(), Undo::ATOMIC);
1355 if (!selection.set()) { // create a dummy selection
1356 selection.end = cursor;
1357 selection.start = cursor;
1360 // Get font setting before we cut
1361 pos_type pos = selection.end.pos();
1362 LyXFont const font = selection.start.par()
1363 ->getFontSettings(bv()->buffer()->params,
1364 selection.start.pos());
1366 // Insert the new string
1367 string::const_iterator cit = str.begin();
1368 string::const_iterator end = str.end();
1369 for (; cit != end; ++cit) {
1370 selection.end.par()->insertChar(pos, (*cit), font);
1374 // Cut the selection
1375 cutSelection(true, false);
1381 // needed to insert the selection
1382 void LyXText::insertStringAsLines(string const & str)
1384 ParagraphList::iterator pit = cursor.par();
1385 pos_type pos = cursor.pos();
1386 ParagraphList::iterator endpit = boost::next(cursor.par());
1388 recordUndo(bv(), Undo::ATOMIC);
1390 // only to be sure, should not be neccessary
1393 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1395 redoParagraphs(cursor.par(), endpit);
1396 setCursor(cursor.par(), cursor.pos());
1397 selection.cursor = cursor;
1398 setCursor(pit, pos);
1403 // turns double-CR to single CR, others where converted into one
1404 // blank. Then InsertStringAsLines is called
1405 void LyXText::insertStringAsParagraphs(string const & str)
1407 string linestr(str);
1408 bool newline_inserted = false;
1409 string::size_type const siz = linestr.length();
1411 for (string::size_type i = 0; i < siz; ++i) {
1412 if (linestr[i] == '\n') {
1413 if (newline_inserted) {
1414 // we know that \r will be ignored by
1415 // InsertStringA. Of course, it is a dirty
1416 // trick, but it works...
1417 linestr[i - 1] = '\r';
1421 newline_inserted = true;
1423 } else if (IsPrintable(linestr[i])) {
1424 newline_inserted = false;
1427 insertStringAsLines(linestr);
1431 bool LyXText::setCursor(ParagraphList::iterator pit,
1433 bool setfont, bool boundary)
1435 LyXCursor old_cursor = cursor;
1436 setCursorIntern(pit, pos, setfont, boundary);
1437 return deleteEmptyParagraphMechanism(old_cursor);
1441 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1442 pos_type pos, bool boundary)
1444 Assert(pit != ownerParagraphs().end());
1448 cur.boundary(boundary);
1452 // get the cursor y position in text
1454 RowList::iterator row = getRow(pit, pos, y);
1455 RowList::iterator beg = rows().begin();
1457 RowList::iterator old_row = row;
1458 // if we are before the first char of this row and are still in the
1459 // same paragraph and there is a previous row then put the cursor on
1460 // the end of the previous row
1461 cur.iy(y + row->baseline());
1464 boost::prior(row)->par() == row->par() &&
1465 pos < pit->size() &&
1466 pit->getChar(pos) == Paragraph::META_INSET) {
1467 InsetOld * ins = pit->getInset(pos);
1468 if (ins && (ins->needFullRow() || ins->display())) {
1474 // y is now the beginning of the cursor row
1475 y += row->baseline();
1476 // y is now the cursor baseline
1479 pos_type last = lastPrintablePos(*this, old_row);
1481 // None of these should happen, but we're scaredy-cats
1482 if (pos > pit->size()) {
1483 lyxerr << "dont like 1 please report" << endl;
1486 } else if (pos > last + 1) {
1487 lyxerr << "dont like 2 please report" << endl;
1488 // This shouldn't happen.
1491 } else if (pos < row->pos()) {
1492 lyxerr << "dont like 3 please report" << endl;
1497 // now get the cursors x position
1498 float x = getCursorX(row, pos, last, boundary);
1501 if (old_row != row) {
1502 x = getCursorX(old_row, pos, last, boundary);
1506 /* We take out this for the time being because 1) the redraw code is not
1507 prepared to this yet and 2) because some good policy has yet to be decided
1508 while editting: for instance how to act on rows being created/deleted
1512 //if the cursor is in a visible row, anchor to it
1514 if (topy < y && y < topy + bv()->workHeight())
1520 float LyXText::getCursorX(RowList::iterator rit,
1521 pos_type pos, pos_type last, bool boundary) const
1523 pos_type cursor_vpos = 0;
1525 double fill_separator;
1527 double fill_label_hfill;
1528 // This call HAS to be here because of the BidiTables!!!
1529 prepareToPrint(rit, x, fill_separator, fill_hfill,
1532 ParagraphList::iterator rit_par = rit->par();
1533 pos_type const rit_pos = rit->pos();
1536 cursor_vpos = rit_pos;
1537 else if (pos > last && !boundary)
1538 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1539 ? rit_pos : last + 1;
1540 else if (pos > rit_pos && (pos > last || boundary))
1541 /// Place cursor after char at (logical) position pos - 1
1542 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1543 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1545 /// Place cursor before char at (logical) position pos
1546 cursor_vpos = (bidi_level(pos) % 2 == 0)
1547 ? log2vis(pos) : log2vis(pos) + 1;
1549 pos_type body_pos = rit_par->beginningOfBody();
1551 (body_pos - 1 > last || !rit_par->isLineSeparator(body_pos - 1)))
1554 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1555 pos_type pos = vis2log(vpos);
1556 if (body_pos > 0 && pos == body_pos - 1) {
1557 x += fill_label_hfill +
1558 font_metrics::width(
1559 rit_par->layout()->labelsep, getLabelFont(rit_par));
1560 if (rit_par->isLineSeparator(body_pos - 1))
1561 x -= singleWidth(rit_par, body_pos - 1);
1564 if (hfillExpansion(*this, rit, pos)) {
1565 x += singleWidth(rit_par, pos);
1566 if (pos >= body_pos)
1569 x += fill_label_hfill;
1570 } else if (rit_par->isSeparator(pos)) {
1571 x += singleWidth(rit_par, pos);
1572 if (pos >= body_pos)
1573 x += fill_separator;
1575 x += singleWidth(rit_par, pos);
1581 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1582 pos_type pos, bool setfont, bool boundary)
1584 UpdatableInset * it = pit->inInset();
1586 if (it != inset_owner) {
1587 lyxerr[Debug::INSETS] << "InsetText is " << it
1589 << "inset_owner is "
1590 << inset_owner << endl;
1591 #ifdef WITH_WARNINGS
1592 #warning I believe this code is wrong. (Lgb)
1593 #warning Jürgen, have a look at this. (Lgb)
1594 #warning Hmmm, I guess you are right but we
1595 #warning should verify when this is needed
1597 // Jürgen, would you like to have a look?
1598 // I guess we need to move the outer cursor
1599 // and open and lock the inset (bla bla bla)
1600 // stuff I don't know... so can you have a look?
1602 // I moved the lyxerr stuff in here so we can see if
1603 // this is actually really needed and where!
1605 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1610 setCursor(cursor, pit, pos, boundary);
1616 void LyXText::setCurrentFont()
1618 pos_type pos = cursor.pos();
1619 ParagraphList::iterator pit = cursor.par();
1621 if (cursor.boundary() && pos > 0)
1625 if (pos == pit->size())
1627 else // potentional bug... BUG (Lgb)
1628 if (pit->isSeparator(pos)) {
1629 if (pos > cursorRow()->pos() &&
1630 bidi_level(pos) % 2 ==
1631 bidi_level(pos - 1) % 2)
1633 else if (pos + 1 < pit->size())
1638 current_font = pit->getFontSettings(bv()->buffer()->params, pos);
1639 real_current_font = getFont(pit, pos);
1641 if (cursor.pos() == pit->size() &&
1642 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1643 !cursor.boundary()) {
1644 Language const * lang =
1645 pit->getParLanguage(bv()->buffer()->params);
1646 current_font.setLanguage(lang);
1647 current_font.setNumber(LyXFont::OFF);
1648 real_current_font.setLanguage(lang);
1649 real_current_font.setNumber(LyXFont::OFF);
1654 // returns the column near the specified x-coordinate of the row
1655 // x is set to the real beginning of this column
1657 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1660 double fill_separator;
1662 double fill_label_hfill;
1664 prepareToPrint(rit, tmpx, fill_separator, fill_hfill, fill_label_hfill);
1666 pos_type vc = rit->pos();
1667 pos_type last = lastPrintablePos(*this, rit);
1670 ParagraphList::iterator rit_par = rit->par();
1671 LyXLayout_ptr const & layout = rit->par()->layout();
1673 bool left_side = false;
1675 pos_type body_pos = rit_par->beginningOfBody();
1676 double last_tmpx = tmpx;
1679 (body_pos - 1 > last ||
1680 !rit_par->isLineSeparator(body_pos - 1)))
1683 // check for empty row
1684 if (!rit_par->size()) {
1689 while (vc <= last && tmpx <= x) {
1692 if (body_pos > 0 && c == body_pos - 1) {
1693 tmpx += fill_label_hfill +
1694 font_metrics::width(layout->labelsep, getLabelFont(rit_par));
1695 if (rit_par->isLineSeparator(body_pos - 1))
1696 tmpx -= singleWidth(rit_par, body_pos - 1);
1699 if (hfillExpansion(*this, rit, c)) {
1700 tmpx += singleWidth(rit_par, c);
1704 tmpx += fill_label_hfill;
1705 } else if (rit_par->isSeparator(c)) {
1706 tmpx += singleWidth(rit_par, c);
1708 tmpx += fill_separator;
1710 tmpx += singleWidth(rit_par, c);
1715 if ((tmpx + last_tmpx) / 2 > x) {
1720 if (vc > last + 1) // This shouldn't happen.
1724 // This (rtl_support test) is not needed, but gives
1725 // some speedup if rtl_support=false
1726 RowList::iterator next_rit = boost::next(rit);
1728 bool const lastrow = lyxrc.rtl_support &&
1729 (next_rit == rowlist_.end() ||
1730 next_rit->par() != rit_par);
1732 // If lastrow is false, we don't need to compute
1733 // the value of rtl.
1734 bool const rtl = (lastrow)
1735 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1738 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1739 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1741 else if (vc == rit->pos()) {
1743 if (bidi_level(c) % 2 == 1)
1746 c = vis2log(vc - 1);
1747 bool const rtl = (bidi_level(c) % 2 == 1);
1748 if (left_side == rtl) {
1750 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1754 if (rit->pos() <= last && c > last
1755 && rit_par->isNewline(last)) {
1756 if (bidi_level(last) % 2 == 0)
1757 tmpx -= singleWidth(rit_par, last);
1759 tmpx += singleWidth(rit_par, last);
1769 void LyXText::setCursorFromCoordinates(int x, int y)
1771 //LyXCursor old_cursor = cursor;
1772 setCursorFromCoordinates(cursor, x, y);
1774 #warning DEPM disabled, otherwise crash when entering new table
1775 //deleteEmptyParagraphMechanism(old_cursor);
1782 * return true if the cursor given is at the end of a row,
1783 * and the next row is filled by an inset that spans an entire
1786 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1788 RowList::iterator row = lt.getRow(cur);
1789 if (boost::next(row) == lt.rows().end())
1792 Row const & next = *boost::next(row);
1794 if (next.pos() != cur.pos() || next.par() != cur.par())
1797 if (cur.pos() == cur.par()->size()
1798 || !cur.par()->isInset(cur.pos()))
1801 InsetOld const * inset = cur.par()->getInset(cur.pos());
1802 if (inset->needFullRow() || inset->display())
1810 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1812 // Get the row first.
1814 RowList::iterator row = getRowNearY(y);
1816 pos_type const column = getColumnNearX(row, x, bound);
1817 cur.par(row->par());
1818 cur.pos(row->pos() + column);
1820 cur.y(y + row->baseline());
1822 if (beforeFullRowInset(*this, cur)) {
1823 pos_type const last = lastPrintablePos(*this, row);
1824 RowList::iterator next_row = boost::next(row);
1826 float x = getCursorX(next_row, cur.pos(), last, bound);
1828 cur.iy(y + row->height() + next_row->baseline());
1833 cur.boundary(bound);
1837 void LyXText::cursorLeft(bool internal)
1839 if (cursor.pos() > 0) {
1840 bool boundary = cursor.boundary();
1841 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1842 if (!internal && !boundary &&
1843 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1844 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1845 } else if (cursor.par() != ownerParagraphs().begin()) {
1846 // steps into the paragraph above
1847 ParagraphList::iterator pit = boost::prior(cursor.par());
1848 setCursor(pit, pit->size());
1853 void LyXText::cursorRight(bool internal)
1855 bool const at_end = (cursor.pos() == cursor.par()->size());
1856 bool const at_newline = !at_end &&
1857 cursor.par()->isNewline(cursor.pos());
1859 if (!internal && cursor.boundary() && !at_newline)
1860 setCursor(cursor.par(), cursor.pos(), true, false);
1862 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1864 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1865 setCursor(cursor.par(), cursor.pos(), true, true);
1866 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
1867 setCursor(boost::next(cursor.par()), 0);
1871 void LyXText::cursorUp(bool selecting)
1874 int x = cursor.x_fix();
1875 int y = cursor.y() - cursorRow()->baseline() - 1;
1876 setCursorFromCoordinates(x, y);
1879 int y1 = cursor.iy() - topy;
1882 InsetOld * inset_hit = checkInsetHit(x, y1);
1883 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1884 inset_hit->localDispatch(
1885 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
1889 setCursorFromCoordinates(bv(), cursor.x_fix(),
1890 cursor.y() - cursorRow()->baseline() - 1);
1895 void LyXText::cursorDown(bool selecting)
1898 int x = cursor.x_fix();
1899 int y = cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1;
1900 setCursorFromCoordinates(x, y);
1901 if (!selecting && cursorRow() == cursorIRow()) {
1903 int y1 = cursor.iy() - topy;
1906 InsetOld * inset_hit = checkInsetHit(x, y1);
1907 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1908 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
1909 inset_hit->localDispatch(cmd);
1913 setCursorFromCoordinates(bv(), cursor.x_fix(),
1914 cursor.y() - cursorRow()->baseline()
1915 + cursorRow()->height() + 1);
1920 void LyXText::cursorUpParagraph()
1922 if (cursor.pos() > 0)
1923 setCursor(cursor.par(), 0);
1924 else if (cursor.par() != ownerParagraphs().begin())
1925 setCursor(boost::prior(cursor.par()), 0);
1929 void LyXText::cursorDownParagraph()
1931 ParagraphList::iterator par = cursor.par();
1932 ParagraphList::iterator next_par = boost::next(par);
1934 if (next_par != ownerParagraphs().end())
1935 setCursor(next_par, 0);
1937 setCursor(par, par->size());
1941 // fix the cursor `cur' after a characters has been deleted at `where'
1942 // position. Called by deleteEmptyParagraphMechanism
1943 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
1944 LyXCursor const & where)
1946 // if cursor is not in the paragraph where the delete occured,
1948 if (cur.par() != where.par())
1951 // if cursor position is after the place where the delete occured,
1953 if (cur.pos() > where.pos())
1954 cur.pos(cur.pos()-1);
1956 // check also if we don't want to set the cursor on a spot behind the
1957 // pagragraph because we erased the last character.
1958 if (cur.pos() > cur.par()->size())
1959 cur.pos(cur.par()->size());
1961 // recompute row et al. for this cursor
1962 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1966 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1968 // Would be wrong to delete anything if we have a selection.
1969 if (selection.set())
1972 // We allow all kinds of "mumbo-jumbo" when freespacing.
1973 if (old_cursor.par()->layout()->free_spacing
1974 || old_cursor.par()->isFreeSpacing()) {
1978 /* Ok I'll put some comments here about what is missing.
1979 I have fixed BackSpace (and thus Delete) to not delete
1980 double-spaces automagically. I have also changed Cut,
1981 Copy and Paste to hopefully do some sensible things.
1982 There are still some small problems that can lead to
1983 double spaces stored in the document file or space at
1984 the beginning of paragraphs. This happens if you have
1985 the cursor betwenn to spaces and then save. Or if you
1986 cut and paste and the selection have a space at the
1987 beginning and then save right after the paste. I am
1988 sure none of these are very hard to fix, but I will
1989 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1990 that I can get some feedback. (Lgb)
1993 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1994 // delete the LineSeparator.
1997 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1998 // delete the LineSeparator.
2001 // If the pos around the old_cursor were spaces, delete one of them.
2002 if (old_cursor.par() != cursor.par()
2003 || old_cursor.pos() != cursor.pos()) {
2004 // Only if the cursor has really moved
2006 if (old_cursor.pos() > 0
2007 && old_cursor.pos() < old_cursor.par()->size()
2008 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2009 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2010 bool erased = old_cursor.par()->erase(old_cursor.pos() - 1);
2011 redoParagraph(old_cursor.par());
2015 #ifdef WITH_WARNINGS
2016 #warning This will not work anymore when we have multiple views of the same buffer
2017 // In this case, we will have to correct also the cursors held by
2018 // other bufferviews. It will probably be easier to do that in a more
2019 // automated way in LyXCursor code. (JMarc 26/09/2001)
2021 // correct all cursors held by the LyXText
2022 fixCursorAfterDelete(cursor, old_cursor);
2023 fixCursorAfterDelete(selection.cursor, old_cursor);
2024 fixCursorAfterDelete(selection.start, old_cursor);
2025 fixCursorAfterDelete(selection.end, old_cursor);
2030 // don't delete anything if this is the ONLY paragraph!
2031 if (ownerParagraphs().size() == 1)
2034 // Do not delete empty paragraphs with keepempty set.
2035 if (old_cursor.par()->allowEmpty())
2038 // only do our magic if we changed paragraph
2039 if (old_cursor.par() == cursor.par())
2042 // record if we have deleted a paragraph
2043 // we can't possibly have deleted a paragraph before this point
2044 bool deleted = false;
2046 if (old_cursor.par()->empty() ||
2047 (old_cursor.par()->size() == 1 &&
2048 old_cursor.par()->isLineSeparator(0))) {
2049 // ok, we will delete anything
2050 LyXCursor tmpcursor;
2054 bool selection_position_was_oldcursor_position = (
2055 selection.cursor.par() == old_cursor.par()
2056 && selection.cursor.pos() == old_cursor.pos());
2058 if (getRow(old_cursor) != rows().begin()) {
2059 RowList::iterator prevrow = boost::prior(getRow(old_cursor));
2061 cursor = old_cursor; // that undo can restore the right cursor position
2062 #warning FIXME. --end() iterator is usable here
2063 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2064 while (endpit != ownerParagraphs().end() &&
2065 endpit->getDepth()) {
2069 recordUndo(bv(), Undo::DELETE, old_cursor.par(),
2070 boost::prior(endpit));
2074 removeRow(getRow(old_cursor));
2076 ownerParagraphs().erase(old_cursor.par());
2078 /* Breakagain the next par. Needed because of
2079 * the parindent that can occur or dissappear.
2080 * The next row can change its height, if
2081 * there is another layout before */
2082 RowList::iterator tmprit = boost::next(prevrow);
2083 if (tmprit != rows().end()) {
2087 setHeightOfRow(prevrow);
2089 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2092 cursor = old_cursor; // that undo can restore the right cursor position
2093 #warning FIXME. --end() iterator is usable here
2094 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2095 while (endpit != ownerParagraphs().end() &&
2096 endpit->getDepth()) {
2100 recordUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2104 removeRow(getRow(old_cursor));
2106 ownerParagraphs().erase(old_cursor.par());
2108 /* Breakagain the next par. Needed because of
2109 the parindent that can occur or dissappear.
2110 The next row can change its height, if
2111 there is another layout before */
2112 if (nextrow != rows().end()) {
2113 breakAgain(nextrow);
2119 setCursorIntern(cursor.par(), cursor.pos());
2121 if (selection_position_was_oldcursor_position) {
2122 // correct selection
2123 selection.cursor = cursor;
2127 if (old_cursor.par()->stripLeadingSpaces()) {
2128 redoParagraph(old_cursor.par());
2130 setCursorIntern(cursor.par(), cursor.pos());
2131 selection.cursor = cursor;
2138 ParagraphList & LyXText::ownerParagraphs() const
2141 return inset_owner->paragraphs;
2143 return bv_owner->buffer()->paragraphs;
2147 bool LyXText::isInInset() const
2149 // Sub-level has non-null bv owner and
2150 // non-null inset owner.
2151 return inset_owner != 0 && bv_owner != 0;
2155 int defaultRowHeight()
2157 LyXFont const font(LyXFont::ALL_SANE);
2158 return int(font_metrics::maxAscent(font)
2159 + font_metrics::maxDescent(font) * 1.5);