1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
16 #include "paragraph.h"
17 #include "funcrequest.h"
18 #include "frontends/LyXView.h"
19 #include "undo_funcs.h"
21 #include "buffer_funcs.h"
22 #include "bufferparams.h"
23 #include "errorlist.h"
25 #include "BufferView.h"
26 #include "CutAndPaste.h"
27 #include "frontends/Painter.h"
28 #include "frontends/font_metrics.h"
32 #include "FloatList.h"
34 #include "ParagraphParameters.h"
36 #include "lyxrow_funcs.h"
37 #include "metricsinfo.h"
38 #include "paragraph_funcs.h"
40 #include "insets/insetbibitem.h"
41 #include "insets/insetenv.h"
42 #include "insets/insetfloat.h"
43 #include "insets/insetwrap.h"
45 #include "support/LAssert.h"
46 #include "support/textutils.h"
47 #include "support/lstrings.h"
49 #include <boost/tuple/tuple.hpp>
53 using namespace lyx::support;
63 LyXText::LyXText(BufferView * bv)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(0), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
72 LyXText::LyXText(BufferView * bv, InsetText * inset)
73 : height(0), width(0), anchor_row_offset_(0),
74 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
76 anchor_row_ = rows().end();
81 void LyXText::init(BufferView * bview)
89 anchor_row_ = rows().end();
90 anchor_row_offset_ = 0;
92 ParagraphList::iterator pit = ownerParagraphs().begin();
93 ParagraphList::iterator end = ownerParagraphs().end();
95 current_font = getFont(pit, 0);
97 for (; pit != end; ++pit)
98 insertParagraph(pit, rowlist_.end());
100 setCursorIntern(rowlist_.begin()->par(), 0);
101 selection.cursor = cursor;
107 // Gets the fully instantiated font at a given position in a paragraph
108 // Basically the same routine as Paragraph::getFont() in paragraph.C.
109 // The difference is that this one is used for displaying, and thus we
110 // are allowed to make cosmetic improvements. For instance make footnotes
112 // If position is -1, we get the layout font of the paragraph.
113 // If position is -2, we get the font of the manual label of the paragraph.
114 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
118 LyXLayout_ptr const & layout = pit->layout();
120 BufferParams const & params = bv()->buffer()->params;
122 // We specialize the 95% common case:
123 if (!pit->getDepth()) {
124 if (layout->labeltype == LABEL_MANUAL
125 && pos < pit->beginningOfBody()) {
127 LyXFont f = pit->getFontSettings(params, pos);
129 pit->inInset()->getDrawFont(f);
130 return f.realize(layout->reslabelfont);
132 LyXFont f = pit->getFontSettings(params, pos);
134 pit->inInset()->getDrawFont(f);
135 return f.realize(layout->resfont);
139 // The uncommon case need not be optimized as much
143 if (pos < pit->beginningOfBody()) {
145 layoutfont = layout->labelfont;
148 layoutfont = layout->font;
151 LyXFont tmpfont = pit->getFontSettings(params, pos);
152 tmpfont.realize(layoutfont);
155 pit->inInset()->getDrawFont(tmpfont);
157 // Realize with the fonts of lesser depth.
158 tmpfont.realize(outerFont(pit, ownerParagraphs()));
159 tmpfont.realize(defaultfont_);
165 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
167 LyXLayout_ptr const & layout = pit->layout();
169 if (!pit->getDepth())
170 return layout->resfont;
172 LyXFont font = layout->font;
173 // Realize with the fonts of lesser depth.
174 font.realize(outerFont(pit, ownerParagraphs()));
175 font.realize(defaultfont_);
181 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
183 LyXLayout_ptr const & layout = pit->layout();
185 if (!pit->getDepth())
186 return layout->reslabelfont;
188 LyXFont font = layout->labelfont;
189 // Realize with the fonts of lesser depth.
190 font.realize(outerFont(pit, ownerParagraphs()));
191 font.realize(defaultfont_);
197 void LyXText::setCharFont(ParagraphList::iterator pit,
198 pos_type pos, LyXFont const & fnt,
201 BufferParams const & params = bv()->buffer()->params;
202 LyXFont font = getFont(pit, pos);
203 font.update(fnt, params.language, toggleall);
204 // Let the insets convert their font
205 if (pit->isInset(pos)) {
206 InsetOld * inset = pit->getInset(pos);
207 if (isEditableInset(inset)) {
208 static_cast<UpdatableInset *>(inset)
209 ->setFont(bv(), fnt, toggleall, true);
213 // Plug through to version below:
214 setCharFont(pit, pos, font);
218 void LyXText::setCharFont(
219 ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
222 LyXLayout_ptr const & layout = pit->layout();
224 // Get concrete layout font to reduce against
227 if (pos < pit->beginningOfBody())
228 layoutfont = layout->labelfont;
230 layoutfont = layout->font;
232 // Realize against environment font information
233 if (pit->getDepth()) {
234 ParagraphList::iterator tp = pit;
235 while (!layoutfont.resolved() &&
236 tp != ownerParagraphs().end() &&
238 tp = outerHook(tp, ownerParagraphs());
239 if (tp != ownerParagraphs().end())
240 layoutfont.realize(tp->layout()->font);
244 layoutfont.realize(defaultfont_);
246 // Now, reduce font against full layout font
247 font.reduce(layoutfont);
249 pit->setFont(pos, font);
253 // removes the row and reset the touched counters
254 void LyXText::removeRow(RowList::iterator rit)
256 if (anchor_row_ == rit) {
257 if (rit != rows().begin()) {
258 anchor_row_ = boost::prior(rit);
259 anchor_row_offset_ += anchor_row_->height();
261 anchor_row_ = boost::next(rit);
262 anchor_row_offset_ -= rit->height();
266 // the text becomes smaller
267 height -= rit->height();
273 // remove all following rows of the paragraph of the specified row.
274 void LyXText::removeParagraph(RowList::iterator rit)
276 ParagraphList::iterator tmppit = rit->par();
279 while (rit != rows().end() && rit->par() == tmppit) {
280 RowList::iterator tmprit = boost::next(rit);
287 void LyXText::insertParagraph(ParagraphList::iterator pit,
288 RowList::iterator rowit)
290 // insert a new row, starting at position 0
292 RowList::iterator rit = rowlist_.insert(rowit, newrow);
294 // and now append the whole paragraph before the new row
295 appendParagraph(rit);
299 InsetOld * LyXText::getInset() const
301 ParagraphList::iterator pit = cursor.par();
302 pos_type const pos = cursor.pos();
304 if (pos < pit->size() && pit->isInset(pos)) {
305 return pit->getInset(pos);
311 void LyXText::toggleInset()
313 InsetOld * inset = getInset();
314 // is there an editable inset at cursor position?
315 if (!isEditableInset(inset)) {
316 // No, try to see if we are inside a collapsable inset
317 if (inset_owner && inset_owner->owner()
318 && inset_owner->owner()->isOpen()) {
319 bv()->unlockInset(inset_owner->owner());
320 inset_owner->owner()->close(bv());
321 bv()->getLyXText()->cursorRight(bv());
325 //bv()->owner()->message(inset->editMessage());
327 // do we want to keep this?? (JMarc)
328 if (!isHighlyEditableInset(inset))
329 recordUndo(bv(), Undo::ATOMIC);
331 if (inset->isOpen()) {
337 bv()->updateInset(inset);
341 /* used in setlayout */
342 // Asger is not sure we want to do this...
343 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
346 LyXLayout_ptr const & layout = par.layout();
347 pos_type const psize = par.size();
350 for (pos_type pos = 0; pos < psize; ++pos) {
351 if (pos < par.beginningOfBody())
352 layoutfont = layout->labelfont;
354 layoutfont = layout->font;
356 LyXFont tmpfont = par.getFontSettings(params, pos);
357 tmpfont.reduce(layoutfont);
358 par.setFont(pos, tmpfont);
363 ParagraphList::iterator
364 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
365 LyXCursor & send_cur,
366 string const & layout)
368 ParagraphList::iterator endpit = boost::next(send_cur.par());
369 ParagraphList::iterator undoendpit = endpit;
370 ParagraphList::iterator pars_end = ownerParagraphs().end();
372 if (endpit != pars_end && endpit->getDepth()) {
373 while (endpit != pars_end && endpit->getDepth()) {
377 } else if (endpit != pars_end) {
378 // because of parindents etc.
382 recordUndo(bv(), Undo::ATOMIC, sstart_cur.par(), boost::prior(undoendpit));
384 // ok we have a selection. This is always between sstart_cur
385 // and sel_end cursor
387 ParagraphList::iterator pit = sstart_cur.par();
388 ParagraphList::iterator epit = boost::next(send_cur.par());
390 LyXLayout_ptr const & lyxlayout =
391 bv()->buffer()->params.getLyXTextClass()[layout];
394 pit->applyLayout(lyxlayout);
395 makeFontEntriesLayoutSpecific(bv()->buffer()->params, *pit);
396 ParagraphList::iterator fppit = pit;
397 fppit->params().spaceTop(lyxlayout->fill_top ?
398 VSpace(VSpace::VFILL)
399 : VSpace(VSpace::NONE));
400 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
401 VSpace(VSpace::VFILL)
402 : VSpace(VSpace::NONE));
403 if (lyxlayout->margintype == MARGIN_MANUAL)
404 pit->setLabelWidthString(lyxlayout->labelstring());
407 } while (pit != epit);
413 // set layout over selection and make a total rebreak of those paragraphs
414 void LyXText::setLayout(string const & layout)
416 LyXCursor tmpcursor = cursor; // store the current cursor
418 // if there is no selection just set the layout
419 // of the current paragraph
420 if (!selection.set()) {
421 selection.start = cursor; // dummy selection
422 selection.end = cursor;
425 // special handling of new environment insets
426 BufferParams const & params = bv()->buffer()->params;
427 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
428 if (lyxlayout->is_environment) {
429 // move everything in a new environment inset
430 lyxerr << "setting layout " << layout << endl;
431 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
432 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
433 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
434 InsetOld * inset = new InsetEnvironment(params, layout);
435 if (bv()->insertInset(inset)) {
437 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
444 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
445 selection.end, layout);
446 redoParagraphs(selection.start.par(), endpit);
448 // we have to reset the selection, because the
449 // geometry could have changed
450 setCursor(selection.start.par(), selection.start.pos(), false);
451 selection.cursor = cursor;
452 setCursor(selection.end.par(), selection.end.pos(), false);
456 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
460 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
462 ParagraphList::iterator pit(cursor.par());
463 ParagraphList::iterator end(cursor.par());
464 ParagraphList::iterator start = pit;
466 if (selection.set()) {
467 pit = selection.start.par();
468 end = selection.end.par();
472 ParagraphList::iterator pastend = boost::next(end);
475 recordUndo(bv(), Undo::ATOMIC, start, end);
477 bool changed = false;
479 int prev_after_depth = 0;
480 #warning parlist ... could be nicer ?
481 if (start != ownerParagraphs().begin()) {
482 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
486 int const depth = pit->params().depth();
487 if (type == bv_funcs::INC_DEPTH) {
488 if (depth < prev_after_depth
489 && pit->layout()->labeltype != LABEL_BIBLIO) {
492 pit->params().depth(depth + 1);
499 pit->params().depth(depth - 1);
502 prev_after_depth = pit->getMaxDepthAfter();
515 redoParagraphs(start, pastend);
517 // We need to actually move the text->cursor. I don't
518 // understand why ...
519 LyXCursor tmpcursor = cursor;
521 // we have to reset the visual selection because the
522 // geometry could have changed
523 if (selection.set()) {
524 setCursor(selection.start.par(), selection.start.pos());
525 selection.cursor = cursor;
526 setCursor(selection.end.par(), selection.end.pos());
529 // this handles the counter labels, and also fixes up
530 // depth values for follow-on (child) paragraphs
534 setCursor(tmpcursor.par(), tmpcursor.pos());
540 // set font over selection and make a total rebreak of those paragraphs
541 void LyXText::setFont(LyXFont const & font, bool toggleall)
543 // if there is no selection just set the current_font
544 if (!selection.set()) {
545 // Determine basis font
547 if (cursor.pos() < cursor.par()->beginningOfBody()) {
548 layoutfont = getLabelFont(cursor.par());
550 layoutfont = getLayoutFont(cursor.par());
552 // Update current font
553 real_current_font.update(font,
554 bv()->buffer()->params.language,
557 // Reduce to implicit settings
558 current_font = real_current_font;
559 current_font.reduce(layoutfont);
560 // And resolve it completely
561 real_current_font.realize(layoutfont);
566 LyXCursor tmpcursor = cursor; // store the current cursor
568 // ok we have a selection. This is always between sel_start_cursor
569 // and sel_end cursor
571 recordUndo(bv(), Undo::ATOMIC, selection.start.par(), selection.end.par());
573 cursor = selection.start;
574 while (cursor.par() != selection.end.par() ||
575 cursor.pos() < selection.end.pos())
577 if (cursor.pos() < cursor.par()->size()) {
578 // an open footnote should behave like a closed one
579 setCharFont(cursor.par(), cursor.pos(),
581 cursor.pos(cursor.pos() + 1);
584 cursor.par(boost::next(cursor.par()));
589 redoParagraph(selection.start.par());
591 // we have to reset the selection, because the
592 // geometry could have changed, but we keep
593 // it for user convenience
594 setCursor(selection.start.par(), selection.start.pos());
595 selection.cursor = cursor;
596 setCursor(selection.end.par(), selection.end.pos());
598 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
599 tmpcursor.boundary());
603 void LyXText::redoHeightOfParagraph()
605 RowList::iterator tmprow = cursorRow();
607 setHeightOfRow(tmprow);
609 while (tmprow != rows().begin()
610 && boost::prior(tmprow)->par() == tmprow->par()) {
612 setHeightOfRow(tmprow);
617 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
621 RowList::iterator LyXText::firstRow(ParagraphList::iterator pit)
623 RowList::iterator rit;
624 for (rit = rows().begin(); rit != rows().end(); ++rit)
625 if (rit->par() == pit)
631 // rebreaks all paragraphs between the specified pars
632 // This function is needed after SetLayout and SetFont etc.
633 void LyXText::redoParagraphs(ParagraphList::iterator start,
634 ParagraphList::iterator endpit)
636 RowList::iterator rit = firstRow(start);
638 if (rit == rows().end()) {
639 lyxerr << "LyXText::redoParagraphs: should not happen\n";
643 ParagraphList::iterator first_phys_pit;
644 RowList::iterator prevrit;
645 if (rit == rows().begin()) {
646 // A trick/hack for UNDO.
647 // This is needed because in an UNDO/REDO we could have
648 // changed the ownerParagraph() so the paragraph inside
649 // the row is NOT my really first par anymore.
650 // Got it Lars ;) (Jug 20011206)
651 first_phys_pit = ownerParagraphs().begin();
652 prevrit = rows().end();
654 first_phys_pit = rit->par();
655 while (rit != rows().begin()
656 && boost::prior(rit)->par() == first_phys_pit)
660 prevrit = boost::prior(rit);
664 while (rit != rows().end() && rit->par() != endpit) {
665 RowList::iterator rit2 = rit++;
669 // Reinsert the paragraphs.
670 ParagraphList::iterator tmppit = first_phys_pit;
672 while (tmppit != ownerParagraphs().end()) {
673 insertParagraph(tmppit, rit);
674 while (rit != rows().end() && rit->par() == tmppit)
677 if (tmppit == endpit)
680 if (prevrit != rows().end())
681 setHeightOfRow(prevrit);
683 setHeightOfRow(rows().begin());
685 if (rit != rows().end())
692 void LyXText::redoParagraph(ParagraphList::iterator pit)
694 redoParagraphs(pit, boost::next(pit));
698 void LyXText::fullRebreak()
701 setCursorIntern(cursor.par(), cursor.pos());
705 void LyXText::metrics(MetricsInfo & mi, Dimension & dim)
707 //lyxerr << "LyXText::metrics: width: " << mi.base.textwidth << "\n";
713 anchor_row_ = rows().end();
714 anchor_row_offset_ = 0;
716 ParagraphList::iterator pit = ownerParagraphs().begin();
717 ParagraphList::iterator end = ownerParagraphs().end();
719 for (; pit != end; ++pit) {
720 InsetList::iterator ii = pit->insetlist.begin();
721 InsetList::iterator iend = pit->insetlist.end();
722 for (; ii != iend; ++ii) {
725 ii->inset->metrics(m, dim);
728 // insert a new row, starting at position 0
730 RowList::iterator rit = rowlist_.insert(rowlist_.end(), newrow);
732 // and now append the whole paragraph before the new row
733 appendParagraph(rit);
737 //lyxerr << "height 0: " << height << "\n";
738 //for (RowList::iterator rit = rows().begin(); rit != rows().end(); ++rit) {
739 // height += rit->height();
741 //lyxerr << "height 1: " << height << "\n";
744 dim.asc = rows().begin()->ascent_of_text();
745 dim.des = height - dim.asc;
746 dim.wid = std::max(mi.base.textwidth, int(width));
750 void LyXText::partialRebreak()
752 if (rows().empty()) {
756 breakAgain(rows().begin());
760 // important for the screen
763 // the cursor set functions have a special mechanism. When they
764 // realize, that you left an empty paragraph, they will delete it.
765 // They also delete the corresponding row
767 // need the selection cursor:
768 void LyXText::setSelection()
770 bool const lsel = TextCursor::setSelection();
772 if (inset_owner && (selection.set() || lsel))
773 inset_owner->setUpdateStatus(InsetText::SELECTION);
778 void LyXText::clearSelection()
780 TextCursor::clearSelection();
782 // reset this in the bv_owner!
783 if (bv_owner && bv_owner->text)
784 bv_owner->text->xsel_cache.set(false);
788 void LyXText::cursorHome()
790 setCursor(cursor.par(), cursorRow()->pos());
794 void LyXText::cursorEnd()
796 if (cursor.par()->empty())
799 RowList::iterator rit = cursorRow();
800 RowList::iterator next_rit = boost::next(rit);
801 ParagraphList::iterator pit = rit->par();
802 pos_type last_pos = lastPos(*this, rit);
804 if (next_rit == rows().end() || next_rit->par() != pit) {
808 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
813 setCursor(pit, last_pos);
817 void LyXText::cursorTop()
819 setCursor(ownerParagraphs().begin(), 0);
823 void LyXText::cursorBottom()
825 ParagraphList::iterator lastpit =
826 boost::prior(ownerParagraphs().end());
827 setCursor(lastpit, lastpit->size());
831 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
833 // If the mask is completely neutral, tell user
834 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
835 // Could only happen with user style
836 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
840 // Try implicit word selection
841 // If there is a change in the language the implicit word selection
843 LyXCursor resetCursor = cursor;
844 bool implicitSelection = (font.language() == ignore_language
845 && font.number() == LyXFont::IGNORE)
846 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
849 setFont(font, toggleall);
851 // Implicit selections are cleared afterwards
852 //and cursor is set to the original position.
853 if (implicitSelection) {
855 cursor = resetCursor;
856 setCursor(cursor.par(), cursor.pos());
857 selection.cursor = cursor;
860 inset_owner->setUpdateStatus(InsetText::CURSOR_PAR);
864 string LyXText::getStringToIndex()
866 // Try implicit word selection
867 // If there is a change in the language the implicit word selection
869 LyXCursor const reset_cursor = cursor;
870 bool const implicitSelection =
871 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
874 if (!selection.set())
875 bv()->owner()->message(_("Nothing to index!"));
876 else if (selection.start.par() != selection.end.par())
877 bv()->owner()->message(_("Cannot index more than one paragraph!"));
879 idxstring = selectionAsString(bv()->buffer(), false);
881 // Reset cursors to their original position.
882 cursor = reset_cursor;
883 setCursor(cursor.par(), cursor.pos());
884 selection.cursor = cursor;
886 // Clear the implicit selection.
887 if (implicitSelection)
894 // the DTP switches for paragraphs. LyX will store them in the first
895 // physicla paragraph. When a paragraph is broken, the top settings rest,
896 // the bottom settings are given to the new one. So I can make shure,
897 // they do not duplicate themself and you cannnot make dirty things with
900 void LyXText::setParagraph(bool line_top, bool line_bottom,
901 bool pagebreak_top, bool pagebreak_bottom,
902 VSpace const & space_top,
903 VSpace const & space_bottom,
904 Spacing const & spacing,
906 string const & labelwidthstring,
909 LyXCursor tmpcursor = cursor;
910 if (!selection.set()) {
911 selection.start = cursor;
912 selection.end = cursor;
915 // make sure that the depth behind the selection are restored, too
916 ParagraphList::iterator endpit = boost::next(selection.end.par());
917 ParagraphList::iterator undoendpit = endpit;
918 ParagraphList::iterator pars_end = ownerParagraphs().end();
920 if (endpit != pars_end && endpit->getDepth()) {
921 while (endpit != pars_end && endpit->getDepth()) {
925 } else if (endpit != pars_end) {
926 // because of parindents etc.
930 recordUndo(bv(), Undo::ATOMIC, selection.start.par(),
931 boost::prior(undoendpit));
934 ParagraphList::iterator tmppit = selection.end.par();
936 while (tmppit != boost::prior(selection.start.par())) {
937 setCursor(tmppit, 0);
939 ParagraphList::iterator pit = cursor.par();
940 ParagraphParameters & params = pit->params();
942 params.lineTop(line_top);
943 params.lineBottom(line_bottom);
944 params.pagebreakTop(pagebreak_top);
945 params.pagebreakBottom(pagebreak_bottom);
946 params.spaceTop(space_top);
947 params.spaceBottom(space_bottom);
948 params.spacing(spacing);
949 // does the layout allow the new alignment?
950 LyXLayout_ptr const & layout = pit->layout();
952 if (align == LYX_ALIGN_LAYOUT)
953 align = layout->align;
954 if (align & layout->alignpossible) {
955 if (align == layout->align)
956 params.align(LYX_ALIGN_LAYOUT);
960 pit->setLabelWidthString(labelwidthstring);
961 params.noindent(noindent);
962 tmppit = boost::prior(pit);
966 redoParagraphs(selection.start.par(), endpit);
969 setCursor(selection.start.par(), selection.start.pos());
970 selection.cursor = cursor;
971 setCursor(selection.end.par(), selection.end.pos());
973 setCursor(tmpcursor.par(), tmpcursor.pos());
975 bv()->updateInset(inset_owner);
979 // set the counter of a paragraph. This includes the labels
980 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
982 LyXTextClass const & textclass = buf->params.getLyXTextClass();
983 LyXLayout_ptr const & layout = pit->layout();
985 if (pit != ownerParagraphs().begin()) {
987 pit->params().appendix(boost::prior(pit)->params().appendix());
988 if (!pit->params().appendix() &&
989 pit->params().startOfAppendix()) {
990 pit->params().appendix(true);
991 textclass.counters().reset();
993 pit->enumdepth = boost::prior(pit)->enumdepth;
994 pit->itemdepth = boost::prior(pit)->itemdepth;
996 pit->params().appendix(pit->params().startOfAppendix());
1001 /* Maybe we have to increment the enumeration depth.
1002 * BUT, enumeration in a footnote is considered in isolation from its
1003 * surrounding paragraph so don't increment if this is the
1004 * first line of the footnote
1005 * AND, bibliographies can't have their depth changed ie. they
1006 * are always of depth 0
1008 if (pit != ownerParagraphs().begin()
1009 && boost::prior(pit)->getDepth() < pit->getDepth()
1010 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1011 && pit->enumdepth < 3
1012 && layout->labeltype != LABEL_BIBLIO) {
1016 // Maybe we have to decrement the enumeration depth, see note above
1017 if (pit != ownerParagraphs().begin()
1018 && boost::prior(pit)->getDepth() > pit->getDepth()
1019 && layout->labeltype != LABEL_BIBLIO) {
1020 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1021 pit->getDepth())->enumdepth;
1024 if (!pit->params().labelString().empty()) {
1025 pit->params().labelString(string());
1028 if (layout->margintype == MARGIN_MANUAL) {
1029 if (pit->params().labelWidthString().empty()) {
1030 pit->setLabelWidthString(layout->labelstring());
1033 pit->setLabelWidthString(string());
1036 // is it a layout that has an automatic label?
1037 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1038 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1042 if (i >= 0 && i <= buf->params.secnumdepth) {
1046 textclass.counters().step(layout->latexname());
1048 // Is there a label? Useful for Chapter layout
1049 if (!pit->params().appendix()) {
1050 s << buf->B_(layout->labelstring());
1052 s << buf->B_(layout->labelstring_appendix());
1055 // Use of an integer is here less than elegant. For now.
1056 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1057 if (!pit->params().appendix()) {
1058 numbertype = "sectioning";
1060 numbertype = "appendix";
1061 if (pit->isRightToLeftPar(buf->params))
1062 langtype = "hebrew";
1068 << textclass.counters()
1069 .numberLabel(layout->latexname(),
1070 numbertype, langtype, head);
1072 pit->params().labelString(STRCONV(s.str()));
1074 // reset enum counters
1075 textclass.counters().reset("enum");
1076 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1077 textclass.counters().reset("enum");
1078 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1080 // Yes I know this is a really, really! bad solution
1082 string enumcounter("enum");
1084 switch (pit->enumdepth) {
1093 enumcounter += "iv";
1096 // not a valid enumdepth...
1100 textclass.counters().step(enumcounter);
1102 s << textclass.counters()
1103 .numberLabel(enumcounter, "enumeration");
1104 pit->params().labelString(STRCONV(s.str()));
1106 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1107 textclass.counters().step("bibitem");
1108 int number = textclass.counters().value("bibitem");
1109 if (pit->bibitem()) {
1110 pit->bibitem()->setCounter(number);
1111 pit->params().labelString(layout->labelstring());
1113 // In biblio should't be following counters but...
1115 string s = buf->B_(layout->labelstring());
1117 // the caption hack:
1118 if (layout->labeltype == LABEL_SENSITIVE) {
1119 ParagraphList::iterator end = ownerParagraphs().end();
1120 ParagraphList::iterator tmppit = pit;
1123 while (tmppit != end && tmppit->inInset()
1124 // the single '=' is intended below
1125 && (in = tmppit->inInset()->owner()))
1127 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
1128 in->lyxCode() == InsetOld::WRAP_CODE) {
1132 tmppit = ownerParagraphs().begin();
1133 for ( ; tmppit != end; ++tmppit)
1134 if (&*tmppit == in->parOwner())
1142 if (in->lyxCode() == InsetOld::FLOAT_CODE)
1143 type = static_cast<InsetFloat*>(in)->params().type;
1144 else if (in->lyxCode() == InsetOld::WRAP_CODE)
1145 type = static_cast<InsetWrap*>(in)->params().type;
1149 Floating const & fl = textclass.floats().getType(type);
1151 textclass.counters().step(fl.type());
1153 // Doesn't work... yet.
1154 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1156 // par->SetLayout(0);
1157 // s = layout->labelstring;
1158 s = _("Senseless: ");
1161 pit->params().labelString(s);
1163 // reset the enumeration counter. They are always reset
1164 // when there is any other layout between
1165 // Just fall-through between the cases so that all
1166 // enum counters deeper than enumdepth is also reset.
1167 switch (pit->enumdepth) {
1169 textclass.counters().reset("enumi");
1171 textclass.counters().reset("enumii");
1173 textclass.counters().reset("enumiii");
1175 textclass.counters().reset("enumiv");
1181 // Updates all counters. Paragraphs with changed label string will be rebroken
1182 void LyXText::updateCounters()
1184 RowList::iterator rowit = rows().begin();
1185 ParagraphList::iterator pit = rowit->par();
1187 // CHECK if this is really needed. (Lgb)
1188 bv()->buffer()->params.getLyXTextClass().counters().reset();
1190 ParagraphList::iterator beg = ownerParagraphs().begin();
1191 ParagraphList::iterator end = ownerParagraphs().end();
1192 for (; pit != end; ++pit) {
1193 while (rowit->par() != pit)
1196 string const oldLabel = pit->params().labelString();
1198 size_t maxdepth = 0;
1200 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1202 if (pit->params().depth() > maxdepth)
1203 pit->params().depth(maxdepth);
1205 // setCounter can potentially change the labelString.
1206 setCounter(bv()->buffer(), pit);
1208 string const & newLabel = pit->params().labelString();
1210 if (oldLabel.empty() && !newLabel.empty()) {
1211 removeParagraph(rowit);
1212 appendParagraph(rowit);
1218 void LyXText::insertInset(InsetOld * inset)
1220 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1222 recordUndo(bv(), Undo::ATOMIC, cursor.par());
1224 cursor.par()->insertInset(cursor.pos(), inset);
1225 // Just to rebreak and refresh correctly.
1226 // The character will not be inserted a second time
1227 insertChar(Paragraph::META_INSET);
1228 // If we enter a highly editable inset the cursor should be to before
1229 // the inset. This couldn't happen before as Undo was not handled inside
1230 // inset now after the Undo LyX tries to call inset->Edit(...) again
1231 // and cannot do this as the cursor is behind the inset and GetInset
1232 // does not return the inset!
1233 if (isHighlyEditableInset(inset)) {
1240 void LyXText::cutSelection(bool doclear, bool realcut)
1242 // Stuff what we got on the clipboard. Even if there is no selection.
1244 // There is a problem with having the stuffing here in that the
1245 // larger the selection the slower LyX will get. This can be
1246 // solved by running the line below only when the selection has
1247 // finished. The solution used currently just works, to make it
1248 // faster we need to be more clever and probably also have more
1249 // calls to stuffClipboard. (Lgb)
1250 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1252 // This doesn't make sense, if there is no selection
1253 if (!selection.set())
1256 // OK, we have a selection. This is always between selection.start
1257 // and selection.end
1259 // make sure that the depth behind the selection are restored, too
1260 ParagraphList::iterator endpit = boost::next(selection.end.par());
1261 ParagraphList::iterator undoendpit = endpit;
1262 ParagraphList::iterator pars_end = ownerParagraphs().end();
1264 if (endpit != pars_end && endpit->getDepth()) {
1265 while (endpit != pars_end && endpit->getDepth()) {
1267 undoendpit = endpit;
1269 } else if (endpit != pars_end) {
1270 // because of parindents etc.
1274 recordUndo(bv(), Undo::DELETE, selection.start.par(),
1275 boost::prior(undoendpit));
1278 endpit = selection.end.par();
1279 int endpos = selection.end.pos();
1281 boost::tie(endpit, endpos) = realcut ?
1282 CutAndPaste::cutSelection(bv()->buffer()->params,
1284 selection.start.par(), endpit,
1285 selection.start.pos(), endpos,
1286 bv()->buffer()->params.textclass,
1288 : CutAndPaste::eraseSelection(bv()->buffer()->params,
1290 selection.start.par(), endpit,
1291 selection.start.pos(), endpos,
1293 // sometimes necessary
1295 selection.start.par()->stripLeadingSpaces();
1297 redoParagraphs(selection.start.par(), boost::next(endpit));
1298 #warning FIXME latent bug
1299 // endpit will be invalidated on redoParagraphs once ParagraphList
1300 // becomes a std::list? There are maybe other places on which this
1301 // can happend? (Ab)
1302 // cutSelection can invalidate the cursor so we need to set
1304 // we prefer the end for when tracking changes
1308 // need a valid cursor. (Lgb)
1311 setCursor(cursor.par(), cursor.pos());
1312 selection.cursor = cursor;
1317 void LyXText::copySelection()
1319 // stuff the selection onto the X clipboard, from an explicit copy request
1320 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1322 // this doesnt make sense, if there is no selection
1323 if (!selection.set())
1326 // ok we have a selection. This is always between selection.start
1327 // and sel_end cursor
1329 // copy behind a space if there is one
1330 while (selection.start.par()->size() > selection.start.pos()
1331 && selection.start.par()->isLineSeparator(selection.start.pos())
1332 && (selection.start.par() != selection.end.par()
1333 || selection.start.pos() < selection.end.pos()))
1334 selection.start.pos(selection.start.pos() + 1);
1336 CutAndPaste::copySelection(selection.start.par(),
1337 selection.end.par(),
1338 selection.start.pos(), selection.end.pos(),
1339 bv()->buffer()->params.textclass);
1343 void LyXText::pasteSelection(size_t sel_index)
1345 // this does not make sense, if there is nothing to paste
1346 if (!CutAndPaste::checkPastePossible())
1349 recordUndo(bv(), Undo::INSERT, cursor.par());
1351 ParagraphList::iterator endpit;
1356 boost::tie(ppp, endpit) =
1357 CutAndPaste::pasteSelection(*bv()->buffer(),
1359 cursor.par(), cursor.pos(),
1360 bv()->buffer()->params.textclass,
1362 bufferErrors(*bv()->buffer(), el);
1363 bv()->showErrorList(_("Paste"));
1365 redoParagraphs(cursor.par(), endpit);
1367 setCursor(cursor.par(), cursor.pos());
1370 selection.cursor = cursor;
1371 setCursor(ppp.first, ppp.second);
1377 void LyXText::setSelectionRange(lyx::pos_type length)
1382 selection.cursor = cursor;
1389 // simple replacing. The font of the first selected character is used
1390 void LyXText::replaceSelectionWithString(string const & str)
1392 recordUndo(bv(), Undo::ATOMIC);
1395 if (!selection.set()) { // create a dummy selection
1396 selection.end = cursor;
1397 selection.start = cursor;
1400 // Get font setting before we cut
1401 pos_type pos = selection.end.pos();
1402 LyXFont const font = selection.start.par()
1403 ->getFontSettings(bv()->buffer()->params,
1404 selection.start.pos());
1406 // Insert the new string
1407 string::const_iterator cit = str.begin();
1408 string::const_iterator end = str.end();
1409 for (; cit != end; ++cit) {
1410 selection.end.par()->insertChar(pos, (*cit), font);
1414 // Cut the selection
1415 cutSelection(true, false);
1421 // needed to insert the selection
1422 void LyXText::insertStringAsLines(string const & str)
1424 ParagraphList::iterator pit = cursor.par();
1425 pos_type pos = cursor.pos();
1426 ParagraphList::iterator endpit = boost::next(cursor.par());
1428 recordUndo(bv(), Undo::ATOMIC);
1430 // only to be sure, should not be neccessary
1433 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1435 redoParagraphs(cursor.par(), endpit);
1436 setCursor(cursor.par(), cursor.pos());
1437 selection.cursor = cursor;
1438 setCursor(pit, pos);
1443 // turns double-CR to single CR, others where converted into one
1444 // blank. Then InsertStringAsLines is called
1445 void LyXText::insertStringAsParagraphs(string const & str)
1447 string linestr(str);
1448 bool newline_inserted = false;
1449 string::size_type const siz = linestr.length();
1451 for (string::size_type i = 0; i < siz; ++i) {
1452 if (linestr[i] == '\n') {
1453 if (newline_inserted) {
1454 // we know that \r will be ignored by
1455 // InsertStringA. Of course, it is a dirty
1456 // trick, but it works...
1457 linestr[i - 1] = '\r';
1461 newline_inserted = true;
1463 } else if (IsPrintable(linestr[i])) {
1464 newline_inserted = false;
1467 insertStringAsLines(linestr);
1471 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1473 breakAgain(getRow(pit, pos));
1475 setCursorIntern(cursor.par(), cursor.pos(), false, cursor.boundary());
1479 // returns false if inset wasn't found
1480 bool LyXText::updateInset(InsetOld * inset)
1482 // first check the current paragraph
1483 int pos = cursor.par()->getPositionOfInset(inset);
1485 checkParagraph(cursor.par(), pos);
1489 // check every paragraph
1490 ParagraphList::iterator par = ownerParagraphs().begin();
1491 ParagraphList::iterator end = ownerParagraphs().end();
1492 for (; par != end; ++par) {
1493 pos = par->getPositionOfInset(inset);
1495 checkParagraph(par, pos);
1504 bool LyXText::setCursor(ParagraphList::iterator pit,
1506 bool setfont, bool boundary)
1508 LyXCursor old_cursor = cursor;
1509 setCursorIntern(pit, pos, setfont, boundary);
1510 return deleteEmptyParagraphMechanism(old_cursor);
1514 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1515 pos_type pos, bool boundary)
1517 Assert(pit != ownerParagraphs().end());
1521 cur.boundary(boundary);
1525 // get the cursor y position in text
1527 RowList::iterator row = getRow(pit, pos, y);
1528 RowList::iterator beg = rows().begin();
1530 RowList::iterator old_row = row;
1531 // if we are before the first char of this row and are still in the
1532 // same paragraph and there is a previous row then put the cursor on
1533 // the end of the previous row
1534 cur.iy(y + row->baseline());
1537 boost::prior(row)->par() == row->par() &&
1538 pos < pit->size() &&
1539 pit->getChar(pos) == Paragraph::META_INSET) {
1540 InsetOld * ins = pit->getInset(pos);
1541 if (ins && (ins->needFullRow() || ins->display())) {
1547 // y is now the beginning of the cursor row
1548 y += row->baseline();
1549 // y is now the cursor baseline
1552 pos_type last = lastPrintablePos(*this, old_row);
1554 // None of these should happen, but we're scaredy-cats
1555 if (pos > pit->size()) {
1556 lyxerr << "dont like 1 please report" << endl;
1559 } else if (pos > last + 1) {
1560 lyxerr << "dont like 2 please report" << endl;
1561 // This shouldn't happen.
1564 } else if (pos < row->pos()) {
1565 lyxerr << "dont like 3 please report" << endl;
1570 // now get the cursors x position
1571 float x = getCursorX(row, pos, last, boundary);
1574 if (old_row != row) {
1575 x = getCursorX(old_row, pos, last, boundary);
1579 /* We take out this for the time being because 1) the redraw code is not
1580 prepared to this yet and 2) because some good policy has yet to be decided
1581 while editting: for instance how to act on rows being created/deleted
1585 //if the cursor is in a visible row, anchor to it
1587 if (topy < y && y < topy + bv()->workHeight())
1593 float LyXText::getCursorX(RowList::iterator rit,
1594 pos_type pos, pos_type last, bool boundary) const
1596 pos_type cursor_vpos = 0;
1598 double fill_separator;
1600 double fill_label_hfill;
1601 // This call HAS to be here because of the BidiTables!!!
1602 prepareToPrint(rit, x, fill_separator, fill_hfill,
1605 ParagraphList::iterator rit_par = rit->par();
1606 pos_type const rit_pos = rit->pos();
1609 cursor_vpos = rit_pos;
1610 else if (pos > last && !boundary)
1611 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1612 ? rit_pos : last + 1;
1613 else if (pos > rit_pos && (pos > last || boundary))
1614 /// Place cursor after char at (logical) position pos - 1
1615 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1616 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1618 /// Place cursor before char at (logical) position pos
1619 cursor_vpos = (bidi_level(pos) % 2 == 0)
1620 ? log2vis(pos) : log2vis(pos) + 1;
1622 pos_type body_pos = rit_par->beginningOfBody();
1624 (body_pos - 1 > last || !rit_par->isLineSeparator(body_pos - 1)))
1627 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1628 pos_type pos = vis2log(vpos);
1629 if (body_pos > 0 && pos == body_pos - 1) {
1630 x += fill_label_hfill +
1631 font_metrics::width(
1632 rit_par->layout()->labelsep, getLabelFont(rit_par));
1633 if (rit_par->isLineSeparator(body_pos - 1))
1634 x -= singleWidth(rit_par, body_pos - 1);
1637 if (hfillExpansion(*this, rit, pos)) {
1638 x += singleWidth(rit_par, pos);
1639 if (pos >= body_pos)
1642 x += fill_label_hfill;
1643 } else if (rit_par->isSeparator(pos)) {
1644 x += singleWidth(rit_par, pos);
1645 if (pos >= body_pos)
1646 x += fill_separator;
1648 x += singleWidth(rit_par, pos);
1654 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1655 pos_type pos, bool setfont, bool boundary)
1657 UpdatableInset * it = pit->inInset();
1659 if (it != inset_owner) {
1660 lyxerr[Debug::INSETS] << "InsetText is " << it
1662 << "inset_owner is "
1663 << inset_owner << endl;
1664 #ifdef WITH_WARNINGS
1665 #warning I believe this code is wrong. (Lgb)
1666 #warning Jürgen, have a look at this. (Lgb)
1667 #warning Hmmm, I guess you are right but we
1668 #warning should verify when this is needed
1670 // Jürgen, would you like to have a look?
1671 // I guess we need to move the outer cursor
1672 // and open and lock the inset (bla bla bla)
1673 // stuff I don't know... so can you have a look?
1675 // I moved the lyxerr stuff in here so we can see if
1676 // this is actually really needed and where!
1678 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1683 setCursor(cursor, pit, pos, boundary);
1689 void LyXText::setCurrentFont()
1691 pos_type pos = cursor.pos();
1692 ParagraphList::iterator pit = cursor.par();
1694 if (cursor.boundary() && pos > 0)
1698 if (pos == pit->size())
1700 else // potentional bug... BUG (Lgb)
1701 if (pit->isSeparator(pos)) {
1702 if (pos > cursorRow()->pos() &&
1703 bidi_level(pos) % 2 ==
1704 bidi_level(pos - 1) % 2)
1706 else if (pos + 1 < pit->size())
1711 current_font = pit->getFontSettings(bv()->buffer()->params, pos);
1712 real_current_font = getFont(pit, pos);
1714 if (cursor.pos() == pit->size() &&
1715 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1716 !cursor.boundary()) {
1717 Language const * lang =
1718 pit->getParLanguage(bv()->buffer()->params);
1719 current_font.setLanguage(lang);
1720 current_font.setNumber(LyXFont::OFF);
1721 real_current_font.setLanguage(lang);
1722 real_current_font.setNumber(LyXFont::OFF);
1727 // returns the column near the specified x-coordinate of the row
1728 // x is set to the real beginning of this column
1730 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1733 double fill_separator;
1735 double fill_label_hfill;
1737 prepareToPrint(rit, tmpx, fill_separator, fill_hfill, fill_label_hfill);
1739 pos_type vc = rit->pos();
1740 pos_type last = lastPrintablePos(*this, rit);
1743 ParagraphList::iterator rit_par = rit->par();
1744 LyXLayout_ptr const & layout = rit->par()->layout();
1746 bool left_side = false;
1748 pos_type body_pos = rit_par->beginningOfBody();
1749 double last_tmpx = tmpx;
1752 (body_pos - 1 > last ||
1753 !rit_par->isLineSeparator(body_pos - 1)))
1756 // check for empty row
1757 if (!rit_par->size()) {
1762 while (vc <= last && tmpx <= x) {
1765 if (body_pos > 0 && c == body_pos - 1) {
1766 tmpx += fill_label_hfill +
1767 font_metrics::width(layout->labelsep, getLabelFont(rit_par));
1768 if (rit_par->isLineSeparator(body_pos - 1))
1769 tmpx -= singleWidth(rit_par, body_pos - 1);
1772 if (hfillExpansion(*this, rit, c)) {
1773 tmpx += singleWidth(rit_par, c);
1777 tmpx += fill_label_hfill;
1778 } else if (rit_par->isSeparator(c)) {
1779 tmpx += singleWidth(rit_par, c);
1781 tmpx += fill_separator;
1783 tmpx += singleWidth(rit_par, c);
1788 if ((tmpx + last_tmpx) / 2 > x) {
1793 if (vc > last + 1) // This shouldn't happen.
1797 // This (rtl_support test) is not needed, but gives
1798 // some speedup if rtl_support=false
1799 RowList::iterator next_rit = boost::next(rit);
1801 bool const lastrow = lyxrc.rtl_support &&
1802 (next_rit == rowlist_.end() ||
1803 next_rit->par() != rit_par);
1805 // If lastrow is false, we don't need to compute
1806 // the value of rtl.
1807 bool const rtl = (lastrow)
1808 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1811 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1812 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1814 else if (vc == rit->pos()) {
1816 if (bidi_level(c) % 2 == 1)
1819 c = vis2log(vc - 1);
1820 bool const rtl = (bidi_level(c) % 2 == 1);
1821 if (left_side == rtl) {
1823 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1827 if (rit->pos() <= last && c > last
1828 && rit_par->isNewline(last)) {
1829 if (bidi_level(last) % 2 == 0)
1830 tmpx -= singleWidth(rit_par, last);
1832 tmpx += singleWidth(rit_par, last);
1842 void LyXText::setCursorFromCoordinates(int x, int y)
1844 //LyXCursor old_cursor = cursor;
1845 setCursorFromCoordinates(cursor, x, y);
1847 #warning DEPM disabled, otherwise crash when entering new table
1848 //deleteEmptyParagraphMechanism(old_cursor);
1855 * return true if the cursor given is at the end of a row,
1856 * and the next row is filled by an inset that spans an entire
1859 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1861 RowList::iterator row = lt.getRow(cur);
1862 if (boost::next(row) == lt.rows().end())
1865 Row const & next = *boost::next(row);
1867 if (next.pos() != cur.pos() || next.par() != cur.par())
1870 if (cur.pos() == cur.par()->size()
1871 || !cur.par()->isInset(cur.pos()))
1874 InsetOld const * inset = cur.par()->getInset(cur.pos());
1875 if (inset->needFullRow() || inset->display())
1883 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1885 // Get the row first.
1887 RowList::iterator row = getRowNearY(y);
1889 pos_type const column = getColumnNearX(row, x, bound);
1890 cur.par(row->par());
1891 cur.pos(row->pos() + column);
1893 cur.y(y + row->baseline());
1895 if (beforeFullRowInset(*this, cur)) {
1896 pos_type const last = lastPrintablePos(*this, row);
1897 RowList::iterator next_row = boost::next(row);
1899 float x = getCursorX(next_row, cur.pos(), last, bound);
1901 cur.iy(y + row->height() + next_row->baseline());
1906 cur.boundary(bound);
1910 void LyXText::cursorLeft(bool internal)
1912 if (cursor.pos() > 0) {
1913 bool boundary = cursor.boundary();
1914 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1915 if (!internal && !boundary &&
1916 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1917 setCursor(cursor.par(), cursor.pos() + 1, true, true);
1918 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
1919 ParagraphList::iterator pit = boost::prior(cursor.par());
1920 setCursor(pit, pit->size());
1925 void LyXText::cursorRight(bool internal)
1927 bool const at_end = (cursor.pos() == cursor.par()->size());
1928 bool const at_newline = !at_end &&
1929 cursor.par()->isNewline(cursor.pos());
1931 if (!internal && cursor.boundary() && !at_newline)
1932 setCursor(cursor.par(), cursor.pos(), true, false);
1934 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1936 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1937 setCursor(cursor.par(), cursor.pos(), true, true);
1938 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
1939 setCursor(boost::next(cursor.par()), 0);
1943 void LyXText::cursorUp(bool selecting)
1946 int x = cursor.x_fix();
1947 int y = cursor.y() - cursorRow()->baseline() - 1;
1948 setCursorFromCoordinates(x, y);
1951 int y1 = cursor.iy() - topy;
1954 InsetOld * inset_hit = checkInsetHit(x, y1);
1955 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1956 inset_hit->localDispatch(
1957 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
1961 setCursorFromCoordinates(bv(), cursor.x_fix(),
1962 cursor.y() - cursorRow()->baseline() - 1);
1967 void LyXText::cursorDown(bool selecting)
1970 int x = cursor.x_fix();
1971 int y = cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1;
1972 setCursorFromCoordinates(x, y);
1973 if (!selecting && cursorRow() == cursorIRow()) {
1975 int y1 = cursor.iy() - topy;
1978 InsetOld * inset_hit = checkInsetHit(x, y1);
1979 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1980 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
1981 inset_hit->localDispatch(cmd);
1985 setCursorFromCoordinates(bv(), cursor.x_fix(),
1986 cursor.y() - cursorRow()->baseline()
1987 + cursorRow()->height() + 1);
1992 void LyXText::cursorUpParagraph()
1994 if (cursor.pos() > 0) {
1995 setCursor(cursor.par(), 0);
1997 else if (cursor.par() != ownerParagraphs().begin()) {
1998 setCursor(boost::prior(cursor.par()), 0);
2003 void LyXText::cursorDownParagraph()
2005 ParagraphList::iterator par = cursor.par();
2006 ParagraphList::iterator next_par = boost::next(par);
2008 if (next_par != ownerParagraphs().end()) {
2009 setCursor(next_par, 0);
2011 setCursor(par, par->size());
2015 // fix the cursor `cur' after a characters has been deleted at `where'
2016 // position. Called by deleteEmptyParagraphMechanism
2017 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2018 LyXCursor const & where)
2020 // if cursor is not in the paragraph where the delete occured,
2022 if (cur.par() != where.par())
2025 // if cursor position is after the place where the delete occured,
2027 if (cur.pos() > where.pos())
2028 cur.pos(cur.pos()-1);
2030 // check also if we don't want to set the cursor on a spot behind the
2031 // pagragraph because we erased the last character.
2032 if (cur.pos() > cur.par()->size())
2033 cur.pos(cur.par()->size());
2035 // recompute row et al. for this cursor
2036 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2040 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2042 // Would be wrong to delete anything if we have a selection.
2043 if (selection.set())
2046 // We allow all kinds of "mumbo-jumbo" when freespacing.
2047 if (old_cursor.par()->layout()->free_spacing
2048 || old_cursor.par()->isFreeSpacing()) {
2052 /* Ok I'll put some comments here about what is missing.
2053 I have fixed BackSpace (and thus Delete) to not delete
2054 double-spaces automagically. I have also changed Cut,
2055 Copy and Paste to hopefully do some sensible things.
2056 There are still some small problems that can lead to
2057 double spaces stored in the document file or space at
2058 the beginning of paragraphs. This happens if you have
2059 the cursor betwenn to spaces and then save. Or if you
2060 cut and paste and the selection have a space at the
2061 beginning and then save right after the paste. I am
2062 sure none of these are very hard to fix, but I will
2063 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2064 that I can get some feedback. (Lgb)
2067 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2068 // delete the LineSeparator.
2071 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2072 // delete the LineSeparator.
2075 // If the pos around the old_cursor were spaces, delete one of them.
2076 if (old_cursor.par() != cursor.par()
2077 || old_cursor.pos() != cursor.pos()) {
2078 // Only if the cursor has really moved
2080 if (old_cursor.pos() > 0
2081 && old_cursor.pos() < old_cursor.par()->size()
2082 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2083 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2084 old_cursor.par()->erase(old_cursor.pos() - 1);
2085 redoParagraph(old_cursor.par());
2087 #ifdef WITH_WARNINGS
2088 #warning This will not work anymore when we have multiple views of the same buffer
2089 // In this case, we will have to correct also the cursors held by
2090 // other bufferviews. It will probably be easier to do that in a more
2091 // automated way in LyXCursor code. (JMarc 26/09/2001)
2093 // correct all cursors held by the LyXText
2094 fixCursorAfterDelete(cursor, old_cursor);
2095 fixCursorAfterDelete(selection.cursor, old_cursor);
2096 fixCursorAfterDelete(selection.start, old_cursor);
2097 fixCursorAfterDelete(selection.end, old_cursor);
2098 fixCursorAfterDelete(last_sel_cursor, old_cursor);
2103 // don't delete anything if this is the ONLY paragraph!
2104 if (ownerParagraphs().size() == 1)
2107 // Do not delete empty paragraphs with keepempty set.
2108 if (old_cursor.par()->allowEmpty())
2111 // only do our magic if we changed paragraph
2112 if (old_cursor.par() == cursor.par())
2115 // record if we have deleted a paragraph
2116 // we can't possibly have deleted a paragraph before this point
2117 bool deleted = false;
2119 if (old_cursor.par()->empty() ||
2120 (old_cursor.par()->size() == 1 &&
2121 old_cursor.par()->isLineSeparator(0))) {
2122 // ok, we will delete anything
2123 LyXCursor tmpcursor;
2127 bool selection_position_was_oldcursor_position = (
2128 selection.cursor.par() == old_cursor.par()
2129 && selection.cursor.pos() == old_cursor.pos());
2131 if (getRow(old_cursor) != rows().begin()) {
2132 RowList::iterator prevrow = boost::prior(getRow(old_cursor));
2135 cursor = old_cursor; // that undo can restore the right cursor position
2136 #warning FIXME. --end() iterator is usable here
2137 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2138 while (endpit != ownerParagraphs().end() &&
2139 endpit->getDepth()) {
2143 recordUndo(bv(), Undo::DELETE, old_cursor.par(),
2144 boost::prior(endpit));
2148 removeRow(getRow(old_cursor));
2150 ownerParagraphs().erase(old_cursor.par());
2152 /* Breakagain the next par. Needed because of
2153 * the parindent that can occur or dissappear.
2154 * The next row can change its height, if
2155 * there is another layout before */
2156 RowList::iterator tmprit = boost::next(prevrow);
2157 if (tmprit != rows().end()) {
2161 setHeightOfRow(prevrow);
2163 RowList::iterator nextrow = boost::next(getRow(old_cursor));
2167 cursor = old_cursor; // that undo can restore the right cursor position
2168 #warning FIXME. --end() iterator is usable here
2169 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2170 while (endpit != ownerParagraphs().end() &&
2171 endpit->getDepth()) {
2175 recordUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2179 removeRow(getRow(old_cursor));
2181 ownerParagraphs().erase(old_cursor.par());
2183 /* Breakagain the next par. Needed because of
2184 the parindent that can occur or dissappear.
2185 The next row can change its height, if
2186 there is another layout before */
2187 if (nextrow != rows().end()) {
2188 breakAgain(nextrow);
2194 setCursorIntern(cursor.par(), cursor.pos());
2196 if (selection_position_was_oldcursor_position) {
2197 // correct selection
2198 selection.cursor = cursor;
2202 if (old_cursor.par()->stripLeadingSpaces()) {
2203 redoParagraph(old_cursor.par());
2205 setCursorIntern(cursor.par(), cursor.pos());
2206 selection.cursor = cursor;
2213 ParagraphList & LyXText::ownerParagraphs() const
2216 return inset_owner->paragraphs;
2218 return bv_owner->buffer()->paragraphs;
2222 bool LyXText::needRefresh() const
2224 return need_refresh_;
2228 void LyXText::clearPaint()
2230 need_refresh_ = false;
2234 void LyXText::postPaint()
2236 need_refresh_ = true;
2238 // We are an inset's lyxtext. Tell the top-level lyxtext
2239 // it needs to update the row we're in.
2241 bv()->text->postPaint();
2245 bool LyXText::isInInset() const
2247 // Sub-level has non-null bv owner and
2248 // non-null inset owner.
2249 return inset_owner != 0 && bv_owner != 0;
2253 int defaultRowHeight()
2255 LyXFont const font(LyXFont::ALL_SANE);
2256 return int(font_metrics::maxAscent(font)
2257 + font_metrics::maxDescent(font) * 1.5);