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 "paragraph_funcs.h"
39 #include "insets/insetbibitem.h"
40 #include "insets/insetenv.h"
41 #include "insets/insetfloat.h"
42 #include "insets/insetwrap.h"
44 #include "support/LAssert.h"
45 #include "support/textutils.h"
46 #include "support/lstrings.h"
48 #include <boost/tuple/tuple.hpp>
50 using namespace lyx::support;
60 LyXText::LyXText(BufferView * bv)
61 : height(0), width(0), anchor_row_offset_(0),
62 inset_owner(0), the_locking_inset(0), bv_owner(bv)
64 anchor_row_ = rows().end();
65 need_break_row = rows().end();
70 LyXText::LyXText(BufferView * bv, InsetText * inset)
71 : height(0), width(0), anchor_row_offset_(0),
72 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
74 anchor_row_ = rows().end();
75 need_break_row = rows().end();
80 void LyXText::init(BufferView * bview)
85 need_break_row = rows().end();
89 anchor_row_ = rows().end();
90 anchor_row_offset_ = 0;
92 ParagraphList::iterator pit = ownerParagraphs().begin();
93 ParagraphList::iterator end = ownerParagraphs().end();
95 current_font = getFont(bview->buffer(), pit, 0);
97 for (; pit != end; ++pit)
98 insertParagraph(pit, rowlist_.end());
100 setCursorIntern(rowlist_.begin()->par(), 0);
101 selection.cursor = cursor;
107 // Gets the fully instantiated font at a given position in a paragraph
108 // Basically the same routine as Paragraph::getFont() in paragraph.C.
109 // The difference is that this one is used for displaying, and thus we
110 // are allowed to make cosmetic improvements. For instance make footnotes
112 // If position is -1, we get the layout font of the paragraph.
113 // If position is -2, we get the font of the manual label of the paragraph.
114 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
119 LyXLayout_ptr const & layout = pit->layout();
121 // We specialize the 95% common case:
122 if (!pit->getDepth()) {
123 if (layout->labeltype == LABEL_MANUAL
124 && pos < pit->beginningOfBody()) {
126 LyXFont f = pit->getFontSettings(buf->params, pos);
128 pit->inInset()->getDrawFont(f);
129 return f.realize(layout->reslabelfont);
131 LyXFont f = pit->getFontSettings(buf->params, pos);
133 pit->inInset()->getDrawFont(f);
134 return f.realize(layout->resfont);
138 // The uncommon case need not be optimized as much
142 if (pos < pit->beginningOfBody()) {
144 layoutfont = layout->labelfont;
147 layoutfont = layout->font;
150 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
151 tmpfont.realize(layoutfont);
154 pit->inInset()->getDrawFont(tmpfont);
156 // Realize with the fonts of lesser depth.
157 tmpfont.realize(outerFont(pit, ownerParagraphs()));
159 return realizeFont(tmpfont, buf->params);
163 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
164 ParagraphList::iterator pit) const
166 LyXLayout_ptr const & layout = pit->layout();
168 if (!pit->getDepth()) {
169 return layout->resfont;
172 LyXFont font(layout->font);
173 // Realize with the fonts of lesser depth.
174 font.realize(outerFont(pit, ownerParagraphs()));
176 return realizeFont(font, buf->params);
180 LyXFont const LyXText::getLabelFont(Buffer const * buf,
181 ParagraphList::iterator pit) const
183 LyXLayout_ptr const & layout = pit->layout();
185 if (!pit->getDepth()) {
186 return layout->reslabelfont;
189 LyXFont font(layout->labelfont);
190 // Realize with the fonts of lesser depth.
191 font.realize(outerFont(pit, ownerParagraphs()));
193 return realizeFont(layout->labelfont, buf->params);
197 void LyXText::setCharFont(ParagraphList::iterator pit,
198 pos_type pos, LyXFont const & fnt,
201 Buffer const * buf = bv()->buffer();
202 LyXFont font = getFont(buf, pit, pos);
203 font.update(fnt, buf->params.language, toggleall);
204 // Let the insets convert their font
205 if (pit->isInset(pos)) {
206 Inset * inset = pit->getInset(pos);
207 if (isEditableInset(inset)) {
208 UpdatableInset * uinset =
209 static_cast<UpdatableInset *>(inset);
210 uinset->setFont(bv(), fnt, toggleall, true);
214 // Plug thru to version below:
215 setCharFont(buf, pit, pos, font);
219 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
220 pos_type pos, LyXFont const & fnt)
224 LyXTextClass const & tclass = buf->params.getLyXTextClass();
225 LyXLayout_ptr const & layout = pit->layout();
227 // Get concrete layout font to reduce against
230 if (pos < pit->beginningOfBody())
231 layoutfont = layout->labelfont;
233 layoutfont = layout->font;
235 // Realize against environment font information
236 if (pit->getDepth()) {
237 ParagraphList::iterator tp = pit;
238 while (!layoutfont.resolved() &&
239 tp != ownerParagraphs().end() &&
241 tp = outerHook(tp, ownerParagraphs());
242 if (tp != ownerParagraphs().end())
243 layoutfont.realize(tp->layout()->font);
247 layoutfont.realize(tclass.defaultfont());
249 // Now, reduce font against full layout font
250 font.reduce(layoutfont);
252 pit->setFont(pos, font);
256 // removes the row and reset the touched counters
257 void LyXText::removeRow(RowList::iterator rit)
259 if (anchor_row_ == rit) {
260 if (rit != rows().begin()) {
261 anchor_row_ = boost::prior(rit);
262 anchor_row_offset_ += anchor_row_->height();
264 anchor_row_ = boost::next(rit);
265 anchor_row_offset_ -= rit->height();
269 // the text becomes smaller
270 height -= rit->height();
276 // remove all following rows of the paragraph of the specified row.
277 void LyXText::removeParagraph(RowList::iterator rit)
279 ParagraphList::iterator tmppit = rit->par();
282 while (rit != rows().end() && rit->par() == tmppit) {
283 RowList::iterator tmprit = boost::next(rit);
290 void LyXText::insertParagraph(ParagraphList::iterator pit,
291 RowList::iterator rowit)
293 // insert a new row, starting at position 0
295 RowList::iterator rit = rowlist_.insert(rowit, newrow);
297 // and now append the whole paragraph before the new row
298 appendParagraph(rit);
302 Inset * LyXText::getInset() const
304 ParagraphList::iterator pit = cursor.par();
305 pos_type const pos = cursor.pos();
307 if (pos < pit->size() && pit->isInset(pos)) {
308 return pit->getInset(pos);
314 void LyXText::toggleInset()
316 Inset * inset = getInset();
317 // is there an editable inset at cursor position?
318 if (!isEditableInset(inset)) {
319 // No, try to see if we are inside a collapsable inset
320 if (inset_owner && inset_owner->owner()
321 && inset_owner->owner()->isOpen()) {
322 bv()->unlockInset(inset_owner->owner());
323 inset_owner->owner()->close(bv());
324 bv()->getLyXText()->cursorRight(bv());
328 //bv()->owner()->message(inset->editMessage());
330 // do we want to keep this?? (JMarc)
331 if (!isHighlyEditableInset(inset))
332 setCursorParUndo(bv());
334 if (inset->isOpen()) {
340 bv()->updateInset(inset);
344 /* used in setlayout */
345 // Asger is not sure we want to do this...
346 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
349 LyXLayout_ptr const & layout = par.layout();
350 pos_type const psize = par.size();
353 for (pos_type pos = 0; pos < psize; ++pos) {
354 if (pos < par.beginningOfBody())
355 layoutfont = layout->labelfont;
357 layoutfont = layout->font;
359 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
360 tmpfont.reduce(layoutfont);
361 par.setFont(pos, tmpfont);
366 ParagraphList::iterator
367 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
368 LyXCursor & send_cur,
369 string const & layout)
371 ParagraphList::iterator endpit = boost::next(send_cur.par());
372 ParagraphList::iterator undoendpit = endpit;
373 ParagraphList::iterator pars_end = ownerParagraphs().end();
375 if (endpit != pars_end && endpit->getDepth()) {
376 while (endpit != pars_end && endpit->getDepth()) {
380 } else if (endpit != pars_end) {
381 // because of parindents etc.
385 setUndo(bv(), Undo::EDIT, sstart_cur.par(), boost::prior(undoendpit));
387 // ok we have a selection. This is always between sstart_cur
388 // and sel_end cursor
390 ParagraphList::iterator pit = sstart_cur.par();
391 ParagraphList::iterator epit = boost::next(send_cur.par());
393 LyXLayout_ptr const & lyxlayout =
394 bv()->buffer()->params.getLyXTextClass()[layout];
397 pit->applyLayout(lyxlayout);
398 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
399 ParagraphList::iterator fppit = pit;
400 fppit->params().spaceTop(lyxlayout->fill_top ?
401 VSpace(VSpace::VFILL)
402 : VSpace(VSpace::NONE));
403 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
404 VSpace(VSpace::VFILL)
405 : VSpace(VSpace::NONE));
406 if (lyxlayout->margintype == MARGIN_MANUAL)
407 pit->setLabelWidthString(lyxlayout->labelstring());
410 } while (pit != epit);
416 // set layout over selection and make a total rebreak of those paragraphs
417 void LyXText::setLayout(string const & layout)
419 LyXCursor tmpcursor = cursor; // store the current cursor
421 // if there is no selection just set the layout
422 // of the current paragraph
423 if (!selection.set()) {
424 selection.start = cursor; // dummy selection
425 selection.end = cursor;
428 // special handling of new environment insets
429 BufferParams const & params = bv()->buffer()->params;
430 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
431 if (lyxlayout->is_environment) {
432 // move everything in a new environment inset
433 lyxerr << "setting layout " << layout << endl;
434 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
435 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
436 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
437 Inset * inset = new InsetEnvironment(params, layout);
438 if (bv()->insertInset(inset)) {
440 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
447 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
448 selection.end, layout);
449 redoParagraphs(selection.start, endpit);
451 // we have to reset the selection, because the
452 // geometry could have changed
453 setCursor(selection.start.par(), selection.start.pos(), false);
454 selection.cursor = cursor;
455 setCursor(selection.end.par(), selection.end.pos(), false);
459 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
463 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
465 ParagraphList::iterator pit(cursor.par());
466 ParagraphList::iterator end(cursor.par());
467 ParagraphList::iterator start = pit;
469 if (selection.set()) {
470 pit = selection.start.par();
471 end = selection.end.par();
475 ParagraphList::iterator pastend = boost::next(end);
478 setUndo(bv(), Undo::EDIT, start, end);
480 bool changed = false;
482 int prev_after_depth = 0;
483 #warning parlist ... could be nicer ?
484 if (start != ownerParagraphs().begin()) {
485 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
489 int const depth = pit->params().depth();
490 if (type == bv_funcs::INC_DEPTH) {
491 if (depth < prev_after_depth
492 && pit->layout()->labeltype != LABEL_BIBLIO) {
495 pit->params().depth(depth + 1);
502 pit->params().depth(depth - 1);
505 prev_after_depth = pit->getMaxDepthAfter();
517 // Wow, redoParagraphs is stupid.
519 setCursor(tmpcursor, start, 0);
521 redoParagraphs(tmpcursor, pastend);
523 // We need to actually move the text->cursor. I don't
524 // understand why ...
527 // we have to reset the visual selection because the
528 // geometry could have changed
529 if (selection.set()) {
530 setCursor(selection.start.par(), selection.start.pos());
531 selection.cursor = cursor;
532 setCursor(selection.end.par(), selection.end.pos());
535 // this handles the counter labels, and also fixes up
536 // depth values for follow-on (child) paragraphs
540 setCursor(tmpcursor.par(), tmpcursor.pos());
546 // set font over selection and make a total rebreak of those paragraphs
547 void LyXText::setFont(LyXFont const & font, bool toggleall)
549 // if there is no selection just set the current_font
550 if (!selection.set()) {
551 // Determine basis font
553 if (cursor.pos() < cursor.par()->beginningOfBody()) {
554 layoutfont = getLabelFont(bv()->buffer(),
557 layoutfont = getLayoutFont(bv()->buffer(),
560 // Update current font
561 real_current_font.update(font,
562 bv()->buffer()->params.language,
565 // Reduce to implicit settings
566 current_font = real_current_font;
567 current_font.reduce(layoutfont);
568 // And resolve it completely
569 real_current_font.realize(layoutfont);
574 LyXCursor tmpcursor = cursor; // store the current cursor
576 // ok we have a selection. This is always between sel_start_cursor
577 // and sel_end cursor
579 setUndo(bv(), Undo::EDIT, selection.start.par(), selection.end.par());
581 cursor = selection.start;
582 while (cursor.par() != selection.end.par() ||
583 cursor.pos() < selection.end.pos())
585 if (cursor.pos() < cursor.par()->size()) {
586 // an open footnote should behave like a closed one
587 setCharFont(cursor.par(), cursor.pos(),
589 cursor.pos(cursor.pos() + 1);
592 cursor.par(boost::next(cursor.par()));
597 redoParagraphs(selection.start, boost::next(selection.end.par()));
599 // we have to reset the selection, because the
600 // geometry could have changed, but we keep
601 // it for user convenience
602 setCursor(selection.start.par(), selection.start.pos());
603 selection.cursor = cursor;
604 setCursor(selection.end.par(), selection.end.pos());
606 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
607 tmpcursor.boundary());
611 void LyXText::redoHeightOfParagraph()
613 RowList::iterator tmprow = cursorRow();
615 setHeightOfRow(tmprow);
617 while (tmprow != rows().begin()
618 && boost::prior(tmprow)->par() == tmprow->par()) {
620 setHeightOfRow(tmprow);
625 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
629 // deletes and inserts again all paragraphs between the cursor
630 // and the specified par
631 // This function is needed after SetLayout and SetFont etc.
632 void LyXText::redoParagraphs(LyXCursor const & cur,
633 ParagraphList::iterator endpit)
635 RowList::iterator tmprit = getRow(cur);
637 ParagraphList::iterator first_phys_pit;
638 RowList::iterator prevrit;
639 if (tmprit == rows().begin()) {
640 // A trick/hack for UNDO.
641 // This is needed because in an UNDO/REDO we could have
642 // changed the ownerParagraph() so the paragraph inside
643 // the row is NOT my really first par anymore.
644 // Got it Lars ;) (Jug 20011206)
645 first_phys_pit = ownerParagraphs().begin();
646 prevrit = rows().end();
648 first_phys_pit = tmprit->par();
649 while (tmprit != rows().begin()
650 && boost::prior(tmprit)->par() == first_phys_pit)
654 prevrit = boost::prior(tmprit);
658 while (tmprit != rows().end() && tmprit->par() != endpit) {
659 RowList::iterator tmprit2 = tmprit++;
663 // Reinsert the paragraphs.
664 ParagraphList::iterator tmppit = first_phys_pit;
666 while (tmppit != ownerParagraphs().end()) {
667 insertParagraph(tmppit, tmprit);
668 while (tmprit != rows().end()
669 && tmprit->par() == tmppit) {
673 if (tmppit == endpit)
676 if (prevrit != rows().end())
677 setHeightOfRow(prevrit);
679 setHeightOfRow(rows().begin());
681 if (tmprit != rows().end())
682 setHeightOfRow(tmprit);
688 void LyXText::fullRebreak()
691 setCursorIntern(cursor.par(), cursor.pos());
695 void LyXText::partialRebreak()
697 if (rows().empty()) {
702 RowList::iterator rows_end = rows().end();
704 if (need_break_row != rows_end) {
705 breakAgain(need_break_row);
706 need_break_row = rows_end;
712 // important for the screen
715 // the cursor set functions have a special mechanism. When they
716 // realize, that you left an empty paragraph, they will delete it.
717 // They also delete the corresponding row
719 // need the selection cursor:
720 void LyXText::setSelection()
722 bool const lsel = TextCursor::setSelection();
724 if (inset_owner && (selection.set() || lsel))
725 inset_owner->setUpdateStatus(InsetText::SELECTION);
730 void LyXText::clearSelection()
732 TextCursor::clearSelection();
734 // reset this in the bv_owner!
735 if (bv_owner && bv_owner->text)
736 bv_owner->text->xsel_cache.set(false);
740 void LyXText::cursorHome()
742 setCursor(cursor.par(), cursorRow()->pos());
746 void LyXText::cursorEnd()
748 if (cursor.par()->empty())
751 RowList::iterator rit = cursorRow();
752 RowList::iterator next_rit = boost::next(rit);
753 ParagraphList::iterator pit = rit->par();
754 pos_type last_pos = lastPos(*this, rit);
756 if (next_rit == rows().end() || next_rit->par() != pit) {
760 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
765 setCursor(pit, last_pos);
769 void LyXText::cursorTop()
771 setCursor(ownerParagraphs().begin(), 0);
775 void LyXText::cursorBottom()
777 ParagraphList::iterator lastpit =
778 boost::prior(ownerParagraphs().end());
779 setCursor(lastpit, lastpit->size());
783 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
785 // If the mask is completely neutral, tell user
786 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
787 // Could only happen with user style
788 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
792 // Try implicit word selection
793 // If there is a change in the language the implicit word selection
795 LyXCursor resetCursor = cursor;
796 bool implicitSelection = (font.language() == ignore_language
797 && font.number() == LyXFont::IGNORE)
798 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
801 setFont(font, toggleall);
803 // Implicit selections are cleared afterwards
804 //and cursor is set to the original position.
805 if (implicitSelection) {
807 cursor = resetCursor;
808 setCursor(cursor.par(), cursor.pos());
809 selection.cursor = cursor;
812 inset_owner->setUpdateStatus(InsetText::CURSOR_PAR);
816 string LyXText::getStringToIndex()
818 // Try implicit word selection
819 // If there is a change in the language the implicit word selection
821 LyXCursor const reset_cursor = cursor;
822 bool const implicitSelection =
823 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
826 if (!selection.set())
827 bv()->owner()->message(_("Nothing to index!"));
828 else if (selection.start.par() != selection.end.par())
829 bv()->owner()->message(_("Cannot index more than one paragraph!"));
831 idxstring = selectionAsString(bv()->buffer(), false);
833 // Reset cursors to their original position.
834 cursor = reset_cursor;
835 setCursor(cursor.par(), cursor.pos());
836 selection.cursor = cursor;
838 // Clear the implicit selection.
839 if (implicitSelection)
846 // the DTP switches for paragraphs. LyX will store them in the first
847 // physicla paragraph. When a paragraph is broken, the top settings rest,
848 // the bottom settings are given to the new one. So I can make shure,
849 // they do not duplicate themself and you cannnot make dirty things with
852 void LyXText::setParagraph(bool line_top, bool line_bottom,
853 bool pagebreak_top, bool pagebreak_bottom,
854 VSpace const & space_top,
855 VSpace const & space_bottom,
856 Spacing const & spacing,
858 string const & labelwidthstring,
861 LyXCursor tmpcursor = cursor;
862 if (!selection.set()) {
863 selection.start = cursor;
864 selection.end = cursor;
867 // make sure that the depth behind the selection are restored, too
868 ParagraphList::iterator endpit = boost::next(selection.end.par());
869 ParagraphList::iterator undoendpit = endpit;
870 ParagraphList::iterator pars_end = ownerParagraphs().end();
872 if (endpit != pars_end && endpit->getDepth()) {
873 while (endpit != pars_end && endpit->getDepth()) {
877 } else if (endpit != pars_end) {
878 // because of parindents etc.
882 setUndo(bv(), Undo::EDIT, selection.start.par(),
883 boost::prior(undoendpit));
886 ParagraphList::iterator tmppit = selection.end.par();
888 while (tmppit != boost::prior(selection.start.par())) {
889 setCursor(tmppit, 0);
891 ParagraphList::iterator pit = cursor.par();
892 ParagraphParameters & params = pit->params();
894 params.lineTop(line_top);
895 params.lineBottom(line_bottom);
896 params.pagebreakTop(pagebreak_top);
897 params.pagebreakBottom(pagebreak_bottom);
898 params.spaceTop(space_top);
899 params.spaceBottom(space_bottom);
900 params.spacing(spacing);
901 // does the layout allow the new alignment?
902 LyXLayout_ptr const & layout = pit->layout();
904 if (align == LYX_ALIGN_LAYOUT)
905 align = layout->align;
906 if (align & layout->alignpossible) {
907 if (align == layout->align)
908 params.align(LYX_ALIGN_LAYOUT);
912 pit->setLabelWidthString(labelwidthstring);
913 params.noindent(noindent);
914 tmppit = boost::prior(pit);
918 redoParagraphs(selection.start, endpit);
921 setCursor(selection.start.par(), selection.start.pos());
922 selection.cursor = cursor;
923 setCursor(selection.end.par(), selection.end.pos());
925 setCursor(tmpcursor.par(), tmpcursor.pos());
927 bv()->updateInset(inset_owner);
931 // set the counter of a paragraph. This includes the labels
932 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
934 LyXTextClass const & textclass = buf->params.getLyXTextClass();
935 LyXLayout_ptr const & layout = pit->layout();
937 if (pit != ownerParagraphs().begin()) {
939 pit->params().appendix(boost::prior(pit)->params().appendix());
940 if (!pit->params().appendix() &&
941 pit->params().startOfAppendix()) {
942 pit->params().appendix(true);
943 textclass.counters().reset();
945 pit->enumdepth = boost::prior(pit)->enumdepth;
946 pit->itemdepth = boost::prior(pit)->itemdepth;
948 pit->params().appendix(pit->params().startOfAppendix());
953 /* Maybe we have to increment the enumeration depth.
954 * BUT, enumeration in a footnote is considered in isolation from its
955 * surrounding paragraph so don't increment if this is the
956 * first line of the footnote
957 * AND, bibliographies can't have their depth changed ie. they
958 * are always of depth 0
960 if (pit != ownerParagraphs().begin()
961 && boost::prior(pit)->getDepth() < pit->getDepth()
962 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
963 && pit->enumdepth < 3
964 && layout->labeltype != LABEL_BIBLIO) {
968 // Maybe we have to decrement the enumeration depth, see note above
969 if (pit != ownerParagraphs().begin()
970 && boost::prior(pit)->getDepth() > pit->getDepth()
971 && layout->labeltype != LABEL_BIBLIO) {
972 pit->enumdepth = depthHook(pit, ownerParagraphs(),
973 pit->getDepth())->enumdepth;
976 if (!pit->params().labelString().empty()) {
977 pit->params().labelString(string());
980 if (layout->margintype == MARGIN_MANUAL) {
981 if (pit->params().labelWidthString().empty()) {
982 pit->setLabelWidthString(layout->labelstring());
985 pit->setLabelWidthString(string());
988 // is it a layout that has an automatic label?
989 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
990 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
994 if (i >= 0 && i <= buf->params.secnumdepth) {
998 textclass.counters().step(layout->latexname());
1000 // Is there a label? Useful for Chapter layout
1001 if (!pit->params().appendix()) {
1002 s << buf->B_(layout->labelstring());
1004 s << buf->B_(layout->labelstring_appendix());
1007 // Use of an integer is here less than elegant. For now.
1008 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1009 if (!pit->params().appendix()) {
1010 numbertype = "sectioning";
1012 numbertype = "appendix";
1013 if (pit->isRightToLeftPar(buf->params))
1014 langtype = "hebrew";
1020 << textclass.counters()
1021 .numberLabel(layout->latexname(),
1022 numbertype, langtype, head);
1024 pit->params().labelString(STRCONV(s.str()));
1026 // reset enum counters
1027 textclass.counters().reset("enum");
1028 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1029 textclass.counters().reset("enum");
1030 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1032 // Yes I know this is a really, really! bad solution
1034 string enumcounter("enum");
1036 switch (pit->enumdepth) {
1045 enumcounter += "iv";
1048 // not a valid enumdepth...
1052 textclass.counters().step(enumcounter);
1054 s << textclass.counters()
1055 .numberLabel(enumcounter, "enumeration");
1056 pit->params().labelString(STRCONV(s.str()));
1058 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1059 textclass.counters().step("bibitem");
1060 int number = textclass.counters().value("bibitem");
1061 if (pit->bibitem()) {
1062 pit->bibitem()->setCounter(number);
1063 pit->params().labelString(layout->labelstring());
1065 // In biblio should't be following counters but...
1067 string s = buf->B_(layout->labelstring());
1069 // the caption hack:
1070 if (layout->labeltype == LABEL_SENSITIVE) {
1071 ParagraphList::iterator tmppit = pit;
1074 while (tmppit != ownerParagraphs().end() &&
1076 // the single '=' is intended below
1077 && (in = tmppit->inInset()->owner())) {
1078 if (in->lyxCode() == Inset::FLOAT_CODE ||
1079 in->lyxCode() == Inset::WRAP_CODE) {
1083 tmppit = std::find(ownerParagraphs().begin(), ownerParagraphs().end(), *in->parOwner());
1090 if (in->lyxCode() == Inset::FLOAT_CODE)
1091 type = static_cast<InsetFloat*>(in)->params().type;
1092 else if (in->lyxCode() == Inset::WRAP_CODE)
1093 type = static_cast<InsetWrap*>(in)->params().type;
1097 Floating const & fl = textclass.floats().getType(type);
1099 textclass.counters().step(fl.type());
1101 // Doesn't work... yet.
1102 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1104 // par->SetLayout(0);
1105 // s = layout->labelstring;
1106 s = _("Senseless: ");
1109 pit->params().labelString(s);
1111 // reset the enumeration counter. They are always reset
1112 // when there is any other layout between
1113 // Just fall-through between the cases so that all
1114 // enum counters deeper than enumdepth is also reset.
1115 switch (pit->enumdepth) {
1117 textclass.counters().reset("enumi");
1119 textclass.counters().reset("enumii");
1121 textclass.counters().reset("enumiii");
1123 textclass.counters().reset("enumiv");
1129 // Updates all counters. Paragraphs with changed label string will be rebroken
1130 void LyXText::updateCounters()
1132 RowList::iterator rowit = rows().begin();
1133 ParagraphList::iterator pit = rowit->par();
1135 // CHECK if this is really needed. (Lgb)
1136 bv()->buffer()->params.getLyXTextClass().counters().reset();
1138 ParagraphList::iterator beg = ownerParagraphs().begin();
1139 ParagraphList::iterator end = ownerParagraphs().end();
1140 for (; pit != end; ++pit) {
1141 while (rowit->par() != pit)
1144 string const oldLabel = pit->params().labelString();
1146 size_t maxdepth = 0;
1148 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1150 if (pit->params().depth() > maxdepth)
1151 pit->params().depth(maxdepth);
1153 // setCounter can potentially change the labelString.
1154 setCounter(bv()->buffer(), pit);
1156 string const & newLabel = pit->params().labelString();
1158 if (oldLabel.empty() && !newLabel.empty()) {
1159 removeParagraph(rowit);
1160 appendParagraph(rowit);
1166 void LyXText::insertInset(Inset * inset)
1168 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1170 setUndo(bv(), Undo::FINISH, cursor.par());
1172 cursor.par()->insertInset(cursor.pos(), inset);
1173 // Just to rebreak and refresh correctly.
1174 // The character will not be inserted a second time
1175 insertChar(Paragraph::META_INSET);
1176 // If we enter a highly editable inset the cursor should be to before
1177 // the inset. This couldn't happen before as Undo was not handled inside
1178 // inset now after the Undo LyX tries to call inset->Edit(...) again
1179 // and cannot do this as the cursor is behind the inset and GetInset
1180 // does not return the inset!
1181 if (isHighlyEditableInset(inset)) {
1188 void LyXText::cutSelection(bool doclear, bool realcut)
1190 // Stuff what we got on the clipboard. Even if there is no selection.
1192 // There is a problem with having the stuffing here in that the
1193 // larger the selection the slower LyX will get. This can be
1194 // solved by running the line below only when the selection has
1195 // finished. The solution used currently just works, to make it
1196 // faster we need to be more clever and probably also have more
1197 // calls to stuffClipboard. (Lgb)
1198 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1200 // This doesn't make sense, if there is no selection
1201 if (!selection.set())
1204 // OK, we have a selection. This is always between selection.start
1205 // and selection.end
1207 // make sure that the depth behind the selection are restored, too
1208 ParagraphList::iterator endpit = boost::next(selection.end.par());
1209 ParagraphList::iterator undoendpit = endpit;
1210 ParagraphList::iterator pars_end = ownerParagraphs().end();
1212 if (endpit != pars_end && endpit->getDepth()) {
1213 while (endpit != pars_end && endpit->getDepth()) {
1215 undoendpit = endpit;
1217 } else if (endpit != pars_end) {
1218 // because of parindents etc.
1222 setUndo(bv(), Undo::DELETE, selection.start.par(),
1223 boost::prior(undoendpit));
1226 endpit = selection.end.par();
1227 int endpos = selection.end.pos();
1229 boost::tie(endpit, endpos) = realcut ?
1230 CutAndPaste::cutSelection(bv()->buffer()->params,
1232 selection.start.par(), endpit,
1233 selection.start.pos(), endpos,
1234 bv()->buffer()->params.textclass,
1236 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1238 selection.start.par(), endpit,
1239 selection.start.pos(), endpos,
1241 // sometimes necessary
1243 selection.start.par()->stripLeadingSpaces();
1245 redoParagraphs(selection.start, boost::next(endpit));
1246 #warning FIXME latent bug
1247 // endpit will be invalidated on redoParagraphs once ParagraphList
1248 // becomes a std::list? There are maybe other places on which this
1249 // can happend? (Ab)
1250 // cutSelection can invalidate the cursor so we need to set
1252 // we prefer the end for when tracking changes
1256 // need a valid cursor. (Lgb)
1259 setCursor(cursor.par(), cursor.pos());
1260 selection.cursor = cursor;
1265 void LyXText::copySelection()
1267 // stuff the selection onto the X clipboard, from an explicit copy request
1268 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1270 // this doesnt make sense, if there is no selection
1271 if (!selection.set())
1274 // ok we have a selection. This is always between selection.start
1275 // and sel_end cursor
1277 // copy behind a space if there is one
1278 while (selection.start.par()->size() > selection.start.pos()
1279 && selection.start.par()->isLineSeparator(selection.start.pos())
1280 && (selection.start.par() != selection.end.par()
1281 || selection.start.pos() < selection.end.pos()))
1282 selection.start.pos(selection.start.pos() + 1);
1284 CutAndPaste::copySelection(selection.start.par(),
1285 selection.end.par(),
1286 selection.start.pos(), selection.end.pos(),
1287 bv()->buffer()->params.textclass);
1291 void LyXText::pasteSelection(size_t sel_index)
1293 // this does not make sense, if there is nothing to paste
1294 if (!CutAndPaste::checkPastePossible())
1297 setUndo(bv(), Undo::INSERT, cursor.par());
1299 ParagraphList::iterator endpit;
1304 boost::tie(ppp, endpit) =
1305 CutAndPaste::pasteSelection(*bv()->buffer(),
1307 cursor.par(), cursor.pos(),
1308 bv()->buffer()->params.textclass,
1310 bufferErrors(*bv()->buffer(), el);
1311 bv()->showErrorList(_("Paste"));
1313 redoParagraphs(cursor, endpit);
1315 setCursor(cursor.par(), cursor.pos());
1318 selection.cursor = cursor;
1319 setCursor(ppp.first, ppp.second);
1325 void LyXText::setSelectionRange(lyx::pos_type length)
1330 selection.cursor = cursor;
1337 // simple replacing. The font of the first selected character is used
1338 void LyXText::replaceSelectionWithString(string const & str)
1340 setCursorParUndo(bv());
1343 if (!selection.set()) { // create a dummy selection
1344 selection.end = cursor;
1345 selection.start = cursor;
1348 // Get font setting before we cut
1349 pos_type pos = selection.end.pos();
1350 LyXFont const font = selection.start.par()
1351 ->getFontSettings(bv()->buffer()->params,
1352 selection.start.pos());
1354 // Insert the new string
1355 string::const_iterator cit = str.begin();
1356 string::const_iterator end = str.end();
1357 for (; cit != end; ++cit) {
1358 selection.end.par()->insertChar(pos, (*cit), font);
1362 // Cut the selection
1363 cutSelection(true, false);
1369 // needed to insert the selection
1370 void LyXText::insertStringAsLines(string const & str)
1372 ParagraphList::iterator pit = cursor.par();
1373 pos_type pos = cursor.pos();
1374 ParagraphList::iterator endpit = boost::next(cursor.par());
1376 setCursorParUndo(bv());
1378 // only to be sure, should not be neccessary
1381 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1383 redoParagraphs(cursor, endpit);
1384 setCursor(cursor.par(), cursor.pos());
1385 selection.cursor = cursor;
1386 setCursor(pit, pos);
1391 // turns double-CR to single CR, others where converted into one
1392 // blank. Then InsertStringAsLines is called
1393 void LyXText::insertStringAsParagraphs(string const & str)
1395 string linestr(str);
1396 bool newline_inserted = false;
1397 string::size_type const siz = linestr.length();
1399 for (string::size_type i = 0; i < siz; ++i) {
1400 if (linestr[i] == '\n') {
1401 if (newline_inserted) {
1402 // we know that \r will be ignored by
1403 // InsertStringA. Of course, it is a dirty
1404 // trick, but it works...
1405 linestr[i - 1] = '\r';
1409 newline_inserted = true;
1411 } else if (IsPrintable(linestr[i])) {
1412 newline_inserted = false;
1415 insertStringAsLines(linestr);
1419 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1421 LyXCursor tmpcursor;
1424 RowList::iterator row = getRow(pit, pos);
1425 RowList::iterator beg = rows().begin();
1427 // is there a break one row above
1428 if (row != beg && boost::prior(row)->par() == row->par()) {
1429 z = rowBreakPoint(*boost::prior(row));
1430 if (z >= row->pos()) {
1431 // set the dimensions of the row above
1434 breakAgain(boost::prior(row));
1436 // set the cursor again. Otherwise
1437 // dangling pointers are possible
1438 setCursor(cursor.par(), cursor.pos(),
1439 false, cursor.boundary());
1440 selection.cursor = cursor;
1448 // set the cursor again. Otherwise dangling pointers are possible
1449 // also set the selection
1451 if (selection.set()) {
1453 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1454 false, selection.cursor.boundary());
1455 selection.cursor = cursor;
1456 setCursorIntern(selection.start.par(),
1457 selection.start.pos(),
1458 false, selection.start.boundary());
1459 selection.start = cursor;
1460 setCursorIntern(selection.end.par(),
1461 selection.end.pos(),
1462 false, selection.end.boundary());
1463 selection.end = cursor;
1464 setCursorIntern(last_sel_cursor.par(),
1465 last_sel_cursor.pos(),
1466 false, last_sel_cursor.boundary());
1467 last_sel_cursor = cursor;
1470 setCursorIntern(cursor.par(), cursor.pos(),
1471 false, cursor.boundary());
1475 // returns false if inset wasn't found
1476 bool LyXText::updateInset(Inset * inset)
1478 // first check the current paragraph
1479 int pos = cursor.par()->getPositionOfInset(inset);
1481 checkParagraph(cursor.par(), pos);
1485 // check every paragraph
1487 ParagraphList::iterator par = ownerParagraphs().begin();
1488 ParagraphList::iterator end = ownerParagraphs().end();
1489 for (; par != end; ++par) {
1490 pos = par->getPositionOfInset(inset);
1492 checkParagraph(par, pos);
1501 bool LyXText::setCursor(ParagraphList::iterator pit,
1503 bool setfont, bool boundary)
1505 LyXCursor old_cursor = cursor;
1506 setCursorIntern(pit, pos, setfont, boundary);
1507 return deleteEmptyParagraphMechanism(old_cursor);
1511 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1512 pos_type pos, bool boundary)
1514 Assert(pit != ownerParagraphs().end());
1518 cur.boundary(boundary);
1520 // get the cursor y position in text
1522 RowList::iterator row = getRow(pit, pos, y);
1523 RowList::iterator beg = rows().begin();
1525 RowList::iterator old_row = row;
1526 // if we are before the first char of this row and are still in the
1527 // same paragraph and there is a previous row then put the cursor on
1528 // the end of the previous row
1529 cur.iy(y + row->baseline());
1532 boost::prior(row)->par() == row->par() &&
1533 pos < pit->size() &&
1534 pit->getChar(pos) == Paragraph::META_INSET) {
1535 Inset * ins = pit->getInset(pos);
1536 if (ins && (ins->needFullRow() || ins->display())) {
1542 // y is now the beginning of the cursor row
1543 y += row->baseline();
1544 // y is now the cursor baseline
1547 pos_type last = lastPrintablePos(*this, old_row);
1549 // None of these should happen, but we're scaredy-cats
1550 if (pos > pit->size()) {
1551 lyxerr << "dont like 1 please report" << endl;
1554 } else if (pos > last + 1) {
1555 lyxerr << "dont like 2 please report" << endl;
1556 // This shouldn't happen.
1559 } else if (pos < row->pos()) {
1560 lyxerr << "dont like 3 please report" << endl;
1565 // now get the cursors x position
1566 float x = getCursorX(row, pos, last, boundary);
1569 if (old_row != row) {
1570 x = getCursorX(old_row, pos, last, boundary);
1574 /* We take out this for the time being because 1) the redraw code is not
1575 prepared to this yet and 2) because some good policy has yet to be decided
1576 while editting: for instance how to act on rows being created/deleted
1580 //if the cursor is in a visible row, anchor to it
1582 if (topy < y && y < topy + bv()->workHeight())
1588 float LyXText::getCursorX(RowList::iterator rit,
1589 pos_type pos, pos_type last, bool boundary) const
1591 pos_type cursor_vpos = 0;
1593 float fill_separator;
1595 float fill_label_hfill;
1596 // This call HAS to be here because of the BidiTables!!!
1597 prepareToPrint(rit, x, fill_separator, fill_hfill,
1600 ParagraphList::iterator rit_par = rit->par();
1601 pos_type const rit_pos = rit->pos();
1604 cursor_vpos = rit_pos;
1605 else if (pos > last && !boundary)
1606 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1607 ? rit_pos : last + 1;
1608 else if (pos > rit_pos && (pos > last || boundary))
1609 /// Place cursor after char at (logical) position pos - 1
1610 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1611 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1613 /// Place cursor before char at (logical) position pos
1614 cursor_vpos = (bidi_level(pos) % 2 == 0)
1615 ? log2vis(pos) : log2vis(pos) + 1;
1617 pos_type body_pos = rit_par->beginningOfBody();
1618 if ((body_pos > 0) &&
1619 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1622 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1623 pos_type pos = vis2log(vpos);
1624 if (body_pos > 0 && pos == body_pos - 1) {
1625 x += fill_label_hfill +
1626 font_metrics::width(
1627 rit_par->layout()->labelsep,
1628 getLabelFont(bv()->buffer(), rit_par));
1629 if (rit_par->isLineSeparator(body_pos - 1))
1630 x -= singleWidth(rit_par, body_pos - 1);
1633 if (hfillExpansion(*this, rit, pos)) {
1634 x += singleWidth(rit_par, pos);
1635 if (pos >= body_pos)
1638 x += fill_label_hfill;
1639 } else if (rit_par->isSeparator(pos)) {
1640 x += singleWidth(rit_par, pos);
1641 if (pos >= body_pos)
1642 x += fill_separator;
1644 x += singleWidth(rit_par, pos);
1650 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1651 pos_type pos, bool setfont, bool boundary)
1653 UpdatableInset * it = pit->inInset();
1655 if (it != inset_owner) {
1656 lyxerr[Debug::INSETS] << "InsetText is " << it
1658 << "inset_owner is "
1659 << inset_owner << endl;
1660 #ifdef WITH_WARNINGS
1661 #warning I believe this code is wrong. (Lgb)
1662 #warning Jürgen, have a look at this. (Lgb)
1663 #warning Hmmm, I guess you are right but we
1664 #warning should verify when this is needed
1666 // Jürgen, would you like to have a look?
1667 // I guess we need to move the outer cursor
1668 // and open and lock the inset (bla bla bla)
1669 // stuff I don't know... so can you have a look?
1671 // I moved the lyxerr stuff in here so we can see if
1672 // this is actually really needed and where!
1674 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1679 setCursor(cursor, pit, pos, boundary);
1685 void LyXText::setCurrentFont()
1687 pos_type pos = cursor.pos();
1688 ParagraphList::iterator pit = cursor.par();
1690 if (cursor.boundary() && pos > 0)
1694 if (pos == pit->size())
1696 else // potentional bug... BUG (Lgb)
1697 if (pit->isSeparator(pos)) {
1698 if (pos > cursorRow()->pos() &&
1699 bidi_level(pos) % 2 ==
1700 bidi_level(pos - 1) % 2)
1702 else if (pos + 1 < pit->size())
1708 pit->getFontSettings(bv()->buffer()->params, pos);
1709 real_current_font = getFont(bv()->buffer(), pit, pos);
1711 if (cursor.pos() == pit->size() &&
1712 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1713 !cursor.boundary()) {
1714 Language const * lang =
1715 pit->getParLanguage(bv()->buffer()->params);
1716 current_font.setLanguage(lang);
1717 current_font.setNumber(LyXFont::OFF);
1718 real_current_font.setLanguage(lang);
1719 real_current_font.setNumber(LyXFont::OFF);
1724 // returns the column near the specified x-coordinate of the row
1725 // x is set to the real beginning of this column
1727 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1730 float fill_separator;
1732 float fill_label_hfill;
1734 prepareToPrint(rit, tmpx, fill_separator,
1735 fill_hfill, fill_label_hfill);
1737 pos_type vc = rit->pos();
1738 pos_type last = lastPrintablePos(*this, rit);
1741 ParagraphList::iterator rit_par = rit->par();
1742 LyXLayout_ptr const & layout = rit->par()->layout();
1744 bool left_side = false;
1746 pos_type body_pos = rit_par->beginningOfBody();
1747 float last_tmpx = tmpx;
1750 (body_pos - 1 > last ||
1751 !rit_par->isLineSeparator(body_pos - 1)))
1754 // check for empty row
1755 if (!rit_par->size()) {
1760 while (vc <= last && tmpx <= x) {
1763 if (body_pos > 0 && c == body_pos - 1) {
1764 tmpx += fill_label_hfill +
1765 font_metrics::width(layout->labelsep,
1766 getLabelFont(bv()->buffer(), rit_par));
1767 if (rit_par->isLineSeparator(body_pos - 1))
1768 tmpx -= singleWidth(rit_par, body_pos - 1);
1771 if (hfillExpansion(*this, rit, c)) {
1772 tmpx += singleWidth(rit_par, c);
1776 tmpx += fill_label_hfill;
1777 } else if (rit_par->isSeparator(c)) {
1778 tmpx += singleWidth(rit_par, c);
1780 tmpx+= fill_separator;
1782 tmpx += singleWidth(rit_par, c);
1787 if ((tmpx + last_tmpx) / 2 > x) {
1792 if (vc > last + 1) // This shouldn't happen.
1796 // This (rtl_support test) is not needed, but gives
1797 // some speedup if rtl_support=false
1798 RowList::iterator next_rit = boost::next(rit);
1800 bool const lastrow = lyxrc.rtl_support &&
1801 (next_rit == rowlist_.end() ||
1802 next_rit->par() != rit_par);
1804 // If lastrow is false, we don't need to compute
1805 // the value of rtl.
1806 bool const rtl = (lastrow)
1807 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1810 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1811 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1813 else if (vc == rit->pos()) {
1815 if (bidi_level(c) % 2 == 1)
1818 c = vis2log(vc - 1);
1819 bool const rtl = (bidi_level(c) % 2 == 1);
1820 if (left_side == rtl) {
1822 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1826 if (rit->pos() <= last && c > last
1827 && rit_par->isNewline(last)) {
1828 if (bidi_level(last) % 2 == 0)
1829 tmpx -= singleWidth(rit_par, last);
1831 tmpx += singleWidth(rit_par, last);
1841 void LyXText::setCursorFromCoordinates(int x, int y)
1843 LyXCursor old_cursor = cursor;
1845 setCursorFromCoordinates(cursor, x, y);
1847 deleteEmptyParagraphMechanism(old_cursor);
1854 * return true if the cursor given is at the end of a row,
1855 * and the next row is filled by an inset that spans an entire
1858 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1860 RowList::iterator row = lt.getRow(cur);
1861 if (boost::next(row) == lt.rows().end())
1864 Row const & next = *boost::next(row);
1866 if (next.pos() != cur.pos() || next.par() != cur.par())
1869 if (cur.pos() == cur.par()->size()
1870 || !cur.par()->isInset(cur.pos()))
1873 Inset const * inset = cur.par()->getInset(cur.pos());
1874 if (inset->needFullRow() || inset->display())
1882 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1884 // Get the row first.
1886 RowList::iterator row = getRowNearY(y);
1888 pos_type const column = getColumnNearX(row, x, bound);
1889 cur.par(row->par());
1890 cur.pos(row->pos() + column);
1892 cur.y(y + row->baseline());
1894 if (beforeFullRowInset(*this, cur)) {
1895 pos_type const last = lastPrintablePos(*this, row);
1896 RowList::iterator next_row = boost::next(row);
1898 float x = getCursorX(next_row, cur.pos(), last, bound);
1900 cur.iy(y + row->height() + next_row->baseline());
1905 cur.boundary(bound);
1909 void LyXText::cursorLeft(bool internal)
1911 if (cursor.pos() > 0) {
1912 bool boundary = cursor.boundary();
1913 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1914 if (!internal && !boundary &&
1915 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1916 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1917 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
1918 ParagraphList::iterator pit = boost::prior(cursor.par());
1919 setCursor(pit, pit->size());
1924 void LyXText::cursorRight(bool internal)
1926 bool const at_end = (cursor.pos() == cursor.par()->size());
1927 bool const at_newline = !at_end &&
1928 cursor.par()->isNewline(cursor.pos());
1930 if (!internal && cursor.boundary() && !at_newline)
1931 setCursor(cursor.par(), cursor.pos(), true, false);
1933 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1935 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1936 setCursor(cursor.par(), cursor.pos(), true, true);
1937 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
1938 setCursor(boost::next(cursor.par()), 0);
1942 void LyXText::cursorUp(bool selecting)
1945 int x = cursor.x_fix();
1946 int y = cursor.y() - cursorRow()->baseline() - 1;
1947 setCursorFromCoordinates(x, y);
1950 int y1 = cursor.iy() - topy;
1953 Inset * inset_hit = checkInsetHit(x, y1);
1954 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1955 inset_hit->localDispatch(
1956 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
1960 setCursorFromCoordinates(bv(), cursor.x_fix(),
1961 cursor.y() - cursorRow()->baseline() - 1);
1966 void LyXText::cursorDown(bool selecting)
1969 int x = cursor.x_fix();
1970 int y = cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1;
1971 setCursorFromCoordinates(x, y);
1972 if (!selecting && cursorRow() == cursorIRow()) {
1974 int y1 = cursor.iy() - topy;
1977 Inset * inset_hit = checkInsetHit(x, y1);
1978 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1979 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
1980 inset_hit->localDispatch(cmd);
1984 setCursorFromCoordinates(bv(), cursor.x_fix(),
1985 cursor.y() - cursorRow()->baseline()
1986 + cursorRow()->height() + 1);
1991 void LyXText::cursorUpParagraph()
1993 if (cursor.pos() > 0) {
1994 setCursor(cursor.par(), 0);
1996 else if (cursor.par() != ownerParagraphs().begin()) {
1997 setCursor(boost::prior(cursor.par()), 0);
2002 void LyXText::cursorDownParagraph()
2004 ParagraphList::iterator par = cursor.par();
2005 ParagraphList::iterator next_par = boost::next(par);
2007 if (next_par != ownerParagraphs().end()) {
2008 setCursor(next_par, 0);
2010 setCursor(par, par->size());
2014 // fix the cursor `cur' after a characters has been deleted at `where'
2015 // position. Called by deleteEmptyParagraphMechanism
2016 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2017 LyXCursor const & where)
2019 // if cursor is not in the paragraph where the delete occured,
2021 if (cur.par() != where.par())
2024 // if cursor position is after the place where the delete occured,
2026 if (cur.pos() > where.pos())
2027 cur.pos(cur.pos()-1);
2029 // check also if we don't want to set the cursor on a spot behind the
2030 // pagragraph because we erased the last character.
2031 if (cur.pos() > cur.par()->size())
2032 cur.pos(cur.par()->size());
2034 // recompute row et al. for this cursor
2035 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2039 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2041 // Would be wrong to delete anything if we have a selection.
2042 if (selection.set())
2045 // We allow all kinds of "mumbo-jumbo" when freespacing.
2046 if (old_cursor.par()->layout()->free_spacing
2047 || old_cursor.par()->isFreeSpacing()) {
2051 /* Ok I'll put some comments here about what is missing.
2052 I have fixed BackSpace (and thus Delete) to not delete
2053 double-spaces automagically. I have also changed Cut,
2054 Copy and Paste to hopefully do some sensible things.
2055 There are still some small problems that can lead to
2056 double spaces stored in the document file or space at
2057 the beginning of paragraphs. This happens if you have
2058 the cursor betwenn to spaces and then save. Or if you
2059 cut and paste and the selection have a space at the
2060 beginning and then save right after the paste. I am
2061 sure none of these are very hard to fix, but I will
2062 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2063 that I can get some feedback. (Lgb)
2066 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2067 // delete the LineSeparator.
2070 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2071 // delete the LineSeparator.
2074 // If the pos around the old_cursor were spaces, delete one of them.
2075 if (old_cursor.par() != cursor.par()
2076 || old_cursor.pos() != cursor.pos()) {
2077 // Only if the cursor has really moved
2079 if (old_cursor.pos() > 0
2080 && old_cursor.pos() < old_cursor.par()->size()
2081 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2082 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2083 old_cursor.par()->erase(old_cursor.pos() - 1);
2084 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2086 #ifdef WITH_WARNINGS
2087 #warning This will not work anymore when we have multiple views of the same buffer
2088 // In this case, we will have to correct also the cursors held by
2089 // other bufferviews. It will probably be easier to do that in a more
2090 // automated way in LyXCursor code. (JMarc 26/09/2001)
2092 // correct all cursors held by the LyXText
2093 fixCursorAfterDelete(cursor, old_cursor);
2094 fixCursorAfterDelete(selection.cursor,
2096 fixCursorAfterDelete(selection.start,
2098 fixCursorAfterDelete(selection.end, old_cursor);
2099 fixCursorAfterDelete(last_sel_cursor,
2101 fixCursorAfterDelete(toggle_cursor, old_cursor);
2102 fixCursorAfterDelete(toggle_end_cursor,
2108 // don't delete anything if this is the ONLY paragraph!
2109 if (ownerParagraphs().size() == 1)
2112 // Do not delete empty paragraphs with keepempty set.
2113 if (old_cursor.par()->allowEmpty())
2116 // only do our magic if we changed paragraph
2117 if (old_cursor.par() == cursor.par())
2120 // record if we have deleted a paragraph
2121 // we can't possibly have deleted a paragraph before this point
2122 bool deleted = false;
2124 if (old_cursor.par()->empty() ||
2125 (old_cursor.par()->size() == 1 &&
2126 old_cursor.par()->isLineSeparator(0))) {
2127 // ok, we will delete anything
2128 LyXCursor tmpcursor;
2132 bool selection_position_was_oldcursor_position = (
2133 selection.cursor.par() == old_cursor.par()
2134 && selection.cursor.pos() == old_cursor.pos());
2136 if (getRow(old_cursor) != rows().begin()) {
2137 RowList::iterator prevrow = boost::prior(getRow(old_cursor));
2140 cursor = old_cursor; // that undo can restore the right cursor position
2141 #warning FIXME. --end() iterator is usable here
2142 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2143 while (endpit != ownerParagraphs().end() &&
2144 endpit->getDepth()) {
2148 setUndo(bv(), Undo::DELETE, old_cursor.par(),
2149 boost::prior(endpit));
2153 removeRow(getRow(old_cursor));
2155 ownerParagraphs().erase(old_cursor.par());
2157 /* Breakagain the next par. Needed because of
2158 * the parindent that can occur or dissappear.
2159 * The next row can change its height, if
2160 * there is another layout before */
2161 RowList::iterator tmprit = boost::next(prevrow);
2162 if (tmprit != rows().end()) {
2166 setHeightOfRow(prevrow);
2168 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2172 cursor = old_cursor; // that undo can restore the right cursor position
2173 #warning FIXME. --end() iterator is usable here
2174 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2175 while (endpit != ownerParagraphs().end() &&
2176 endpit->getDepth()) {
2180 setUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2184 removeRow(getRow(old_cursor));
2186 ownerParagraphs().erase(old_cursor.par());
2188 /* Breakagain the next par. Needed because of
2189 the parindent that can occur or dissappear.
2190 The next row can change its height, if
2191 there is another layout before */
2192 if (nextrow != rows().end()) {
2193 breakAgain(nextrow);
2199 setCursorIntern(cursor.par(), cursor.pos());
2201 if (selection_position_was_oldcursor_position) {
2202 // correct selection
2203 selection.cursor = cursor;
2207 if (old_cursor.par()->stripLeadingSpaces()) {
2208 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2210 setCursorIntern(cursor.par(), cursor.pos());
2211 selection.cursor = cursor;
2218 ParagraphList & LyXText::ownerParagraphs() const
2221 return inset_owner->paragraphs;
2223 return bv_owner->buffer()->paragraphs;
2227 bool LyXText::needRefresh() const
2229 return need_refresh_;
2233 void LyXText::clearPaint()
2235 need_refresh_ = false;
2239 void LyXText::postPaint()
2241 need_refresh_ = true;
2243 // We are an inset's lyxtext. Tell the top-level lyxtext
2244 // it needs to update the row we're in.
2246 bv()->text->postPaint();
2250 bool LyXText::isInInset() const
2252 // Sub-level has non-null bv owner and
2253 // non-null inset owner.
2254 return inset_owner != 0 && bv_owner != 0;
2258 int defaultRowHeight()
2260 LyXFont const font(LyXFont::ALL_SANE);
2261 return int(font_metrics::maxAscent(font)
2262 + font_metrics::maxDescent(font) * 1.5);