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 "bufferparams.h"
22 #include "errorlist.h"
24 #include "BufferView.h"
25 #include "CutAndPaste.h"
26 #include "frontends/Painter.h"
27 #include "frontends/font_metrics.h"
31 #include "FloatList.h"
33 #include "ParagraphParameters.h"
35 #include "lyxrow_funcs.h"
36 #include "paragraph_funcs.h"
38 #include "insets/insetbibitem.h"
39 #include "insets/insetenv.h"
40 #include "insets/insetfloat.h"
41 #include "insets/insetwrap.h"
43 #include "support/LAssert.h"
44 #include "support/textutils.h"
45 #include "support/lstrings.h"
47 #include <boost/tuple/tuple.hpp>
57 LyXText::LyXText(BufferView * bv)
58 : height(0), width(0), anchor_row_offset_(0),
59 inset_owner(0), the_locking_inset(0), bv_owner(bv)
61 anchor_row_ = rows().end();
62 need_break_row = rows().end();
63 refresh_row = rows().end();
69 LyXText::LyXText(BufferView * bv, InsetText * inset)
70 : height(0), width(0), anchor_row_offset_(0),
71 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
73 anchor_row_ = rows().end();
74 need_break_row = rows().end();
75 refresh_row = rows().end();
81 void LyXText::init(BufferView * bview, bool reinit)
85 need_break_row = rows().end();
89 } else if (!rowlist_.empty())
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,
117 lyx::Assert(pos >= 0);
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 /* FIXME: when we cache the bview, this should just
260 * become a postPaint(), I think */
261 if (refresh_row == rit) {
262 if (rit == rows().begin())
263 refresh_row = boost::next(rit);
265 refresh_row = boost::prior(rit);
267 // what about refresh_y
270 if (anchor_row_ == rit) {
271 if (rit != rows().begin()) {
272 anchor_row_ = boost::prior(rit);
273 anchor_row_offset_ += anchor_row_->height();
275 anchor_row_ = boost::next(rit);
276 anchor_row_offset_ -= rit->height();
280 // the text becomes smaller
281 height -= rit->height();
287 // remove all following rows of the paragraph of the specified row.
288 void LyXText::removeParagraph(RowList::iterator rit)
290 ParagraphList::iterator tmppit = rit->par();
293 while (rit != rows().end() && rit->par() == tmppit) {
294 RowList::iterator tmprit = boost::next(rit);
301 void LyXText::insertParagraph(ParagraphList::iterator pit,
302 RowList::iterator rowit)
304 // insert a new row, starting at position 0
306 RowList::iterator rit = rowlist_.insert(rowit, newrow);
308 // and now append the whole paragraph before the new row
309 appendParagraph(rit);
313 Inset * LyXText::getInset() const
315 ParagraphList::iterator pit = cursor.par();
316 pos_type const pos = cursor.pos();
318 if (pos < pit->size() && pit->isInset(pos)) {
319 return pit->getInset(pos);
325 void LyXText::toggleInset()
327 Inset * inset = getInset();
328 // is there an editable inset at cursor position?
329 if (!isEditableInset(inset)) {
330 // No, try to see if we are inside a collapsable inset
331 if (inset_owner && inset_owner->owner()
332 && inset_owner->owner()->isOpen()) {
333 bv()->unlockInset(inset_owner->owner());
334 inset_owner->owner()->close(bv());
335 bv()->getLyXText()->cursorRight(bv());
339 //bv()->owner()->message(inset->editMessage());
341 // do we want to keep this?? (JMarc)
342 if (!isHighlyEditableInset(inset))
343 setCursorParUndo(bv());
345 if (inset->isOpen()) {
351 bv()->updateInset(inset);
355 /* used in setlayout */
356 // Asger is not sure we want to do this...
357 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
360 LyXLayout_ptr const & layout = par.layout();
361 pos_type const psize = par.size();
364 for (pos_type pos = 0; pos < psize; ++pos) {
365 if (pos < par.beginningOfBody())
366 layoutfont = layout->labelfont;
368 layoutfont = layout->font;
370 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
371 tmpfont.reduce(layoutfont);
372 par.setFont(pos, tmpfont);
377 ParagraphList::iterator
378 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
379 LyXCursor & send_cur,
380 string const & layout)
382 ParagraphList::iterator endpit = boost::next(send_cur.par());
383 ParagraphList::iterator undoendpit = endpit;
384 ParagraphList::iterator pars_end = ownerParagraphs().end();
386 if (endpit != pars_end && endpit->getDepth()) {
387 while (endpit != pars_end && endpit->getDepth()) {
391 } else if (endpit != pars_end) {
392 // because of parindents etc.
396 setUndo(bv(), Undo::EDIT, sstart_cur.par(), boost::prior(undoendpit));
398 // ok we have a selection. This is always between sstart_cur
399 // and sel_end cursor
401 ParagraphList::iterator pit = sstart_cur.par();
402 ParagraphList::iterator epit = boost::next(send_cur.par());
404 LyXLayout_ptr const & lyxlayout =
405 bv()->buffer()->params.getLyXTextClass()[layout];
408 pit->applyLayout(lyxlayout);
409 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
410 ParagraphList::iterator fppit = pit;
411 fppit->params().spaceTop(lyxlayout->fill_top ?
412 VSpace(VSpace::VFILL)
413 : VSpace(VSpace::NONE));
414 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
415 VSpace(VSpace::VFILL)
416 : VSpace(VSpace::NONE));
417 if (lyxlayout->margintype == MARGIN_MANUAL)
418 pit->setLabelWidthString(lyxlayout->labelstring());
421 } while (pit != epit);
427 // set layout over selection and make a total rebreak of those paragraphs
428 void LyXText::setLayout(string const & layout)
430 LyXCursor tmpcursor = cursor; // store the current cursor
432 // if there is no selection just set the layout
433 // of the current paragraph
434 if (!selection.set()) {
435 selection.start = cursor; // dummy selection
436 selection.end = cursor;
439 // special handling of new environment insets
440 BufferParams const & params = bv()->buffer()->params;
441 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
442 if (lyxlayout->is_environment) {
443 // move everything in a new environment inset
444 lyxerr << "setting layout " << layout << endl;
445 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
446 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
447 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
448 Inset * inset = new InsetEnvironment(params, layout);
449 if (bv()->insertInset(inset)) {
451 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
458 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
459 selection.end, layout);
460 redoParagraphs(selection.start, endpit);
462 // we have to reset the selection, because the
463 // geometry could have changed
464 setCursor(selection.start.par(), selection.start.pos(), false);
465 selection.cursor = cursor;
466 setCursor(selection.end.par(), selection.end.pos(), false);
470 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
474 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
476 ParagraphList::iterator pit(cursor.par());
477 ParagraphList::iterator end(cursor.par());
478 ParagraphList::iterator start = pit;
480 if (selection.set()) {
481 pit = selection.start.par();
482 end = selection.end.par();
486 ParagraphList::iterator pastend = boost::next(end);
489 setUndo(bv(), Undo::EDIT, start, end);
491 bool changed = false;
493 int prev_after_depth = 0;
494 #warning parlist ... could be nicer ?
495 if (start != ownerParagraphs().begin()) {
496 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
500 int const depth = pit->params().depth();
501 if (type == bv_funcs::INC_DEPTH) {
502 if (depth < prev_after_depth
503 && pit->layout()->labeltype != LABEL_BIBLIO) {
506 pit->params().depth(depth + 1);
513 pit->params().depth(depth - 1);
516 prev_after_depth = pit->getMaxDepthAfter();
528 // Wow, redoParagraphs is stupid.
530 setCursor(tmpcursor, start, 0);
532 //redoParagraphs(tmpcursor, &(*pastend));
533 redoParagraphs(tmpcursor, pastend);
535 // We need to actually move the text->cursor. I don't
536 // understand why ...
539 // we have to reset the visual selection because the
540 // geometry could have changed
541 if (selection.set()) {
542 setCursor(selection.start.par(), selection.start.pos());
543 selection.cursor = cursor;
544 setCursor(selection.end.par(), selection.end.pos());
547 // this handles the counter labels, and also fixes up
548 // depth values for follow-on (child) paragraphs
552 setCursor(tmpcursor.par(), tmpcursor.pos());
558 // set font over selection and make a total rebreak of those paragraphs
559 void LyXText::setFont(LyXFont const & font, bool toggleall)
561 // if there is no selection just set the current_font
562 if (!selection.set()) {
563 // Determine basis font
565 if (cursor.pos() < cursor.par()->beginningOfBody()) {
566 layoutfont = getLabelFont(bv()->buffer(),
569 layoutfont = getLayoutFont(bv()->buffer(),
572 // Update current font
573 real_current_font.update(font,
574 bv()->buffer()->params.language,
577 // Reduce to implicit settings
578 current_font = real_current_font;
579 current_font.reduce(layoutfont);
580 // And resolve it completely
581 real_current_font.realize(layoutfont);
586 LyXCursor tmpcursor = cursor; // store the current cursor
588 // ok we have a selection. This is always between sel_start_cursor
589 // and sel_end cursor
591 setUndo(bv(), Undo::EDIT, selection.start.par(), selection.end.par());
593 cursor = selection.start;
594 while (cursor.par() != selection.end.par() ||
595 cursor.pos() < selection.end.pos())
597 if (cursor.pos() < cursor.par()->size()) {
598 // an open footnote should behave like a closed one
599 setCharFont(cursor.par(), cursor.pos(),
601 cursor.pos(cursor.pos() + 1);
604 cursor.par(boost::next(cursor.par()));
609 redoParagraphs(selection.start, boost::next(selection.end.par()));
611 // we have to reset the selection, because the
612 // geometry could have changed, but we keep
613 // it for user convenience
614 setCursor(selection.start.par(), selection.start.pos());
615 selection.cursor = cursor;
616 setCursor(selection.end.par(), selection.end.pos());
618 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
619 tmpcursor.boundary());
623 void LyXText::redoHeightOfParagraph()
625 RowList::iterator tmprow = cursor.row();
626 int y = cursor.y() - tmprow->baseline();
628 setHeightOfRow(tmprow);
630 while (tmprow != rows().begin()
631 && boost::prior(tmprow)->par() == tmprow->par()) {
633 y -= tmprow->height();
634 setHeightOfRow(tmprow);
639 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
643 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
645 RowList::iterator tmprow = cur.row();
647 int y = cur.y() - tmprow->baseline();
648 setHeightOfRow(tmprow);
650 while (tmprow != rows().begin()
651 && boost::prior(tmprow)->par() == tmprow->par()) {
653 y -= tmprow->height();
657 setCursor(cur.par(), cur.pos());
661 // deletes and inserts again all paragaphs between the cursor
662 // and the specified par
663 // This function is needed after SetLayout and SetFont etc.
664 void LyXText::redoParagraphs(LyXCursor const & cur,
665 ParagraphList::iterator endpit)
667 RowList::iterator tmprit = cur.row();
668 int y = cur.y() - tmprit->baseline();
670 ParagraphList::iterator first_phys_pit;
671 RowList::iterator prevrit;
672 if (tmprit == rows().begin()) {
673 // A trick/hack for UNDO.
674 // This is needed because in an UNDO/REDO we could have
675 // changed the ownerParagrah() so the paragraph inside
676 // the row is NOT my really first par anymore.
677 // Got it Lars ;) (Jug 20011206)
678 first_phys_pit = ownerParagraphs().begin();
679 prevrit = rows().end();
681 first_phys_pit = tmprit->par();
682 while (tmprit != rows().begin()
683 && boost::prior(tmprit)->par() == first_phys_pit)
686 y -= tmprit->height();
688 prevrit = boost::prior(tmprit);
692 while (tmprit != rows().end() && tmprit->par() != endpit) {
693 RowList::iterator tmprit2 = tmprit++;
697 // Reinsert the paragraphs.
698 ParagraphList::iterator tmppit = first_phys_pit;
700 while (tmppit != ownerParagraphs().end()) {
701 insertParagraph(tmppit, tmprit);
702 while (tmprit != rows().end()
703 && tmprit->par() == tmppit) {
707 if (tmppit == endpit)
710 if (prevrit != rows().end()) {
711 setHeightOfRow(prevrit);
712 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
714 setHeightOfRow(rows().begin());
715 const_cast<LyXText *>(this)->postPaint(0);
717 if (tmprit != rows().end())
718 setHeightOfRow(tmprit);
724 void LyXText::fullRebreak()
726 if (rows().empty()) {
731 RowList::iterator rows_end = rows().end();
733 if (need_break_row != rows_end) {
734 breakAgain(need_break_row);
735 need_break_row = rows_end;
741 // important for the screen
744 // the cursor set functions have a special mechanism. When they
745 // realize, that you left an empty paragraph, they will delete it.
746 // They also delete the corresponding row
748 // need the selection cursor:
749 void LyXText::setSelection()
751 bool const lsel = selection.set();
753 if (!selection.set()) {
754 last_sel_cursor = selection.cursor;
755 selection.start = selection.cursor;
756 selection.end = selection.cursor;
761 // first the toggling area
762 if (cursor.y() < last_sel_cursor.y()
763 || (cursor.y() == last_sel_cursor.y()
764 && cursor.x() < last_sel_cursor.x())) {
765 toggle_end_cursor = last_sel_cursor;
766 toggle_cursor = cursor;
768 toggle_end_cursor = cursor;
769 toggle_cursor = last_sel_cursor;
772 last_sel_cursor = cursor;
774 // and now the whole selection
776 if (selection.cursor.par() == cursor.par())
777 if (selection.cursor.pos() < cursor.pos()) {
778 selection.end = cursor;
779 selection.start = selection.cursor;
781 selection.end = selection.cursor;
782 selection.start = cursor;
784 else if (selection.cursor.y() < cursor.y() ||
785 (selection.cursor.y() == cursor.y()
786 && selection.cursor.x() < cursor.x())) {
787 selection.end = cursor;
788 selection.start = selection.cursor;
791 selection.end = selection.cursor;
792 selection.start = cursor;
795 // a selection with no contents is not a selection
796 if (selection.start.par() == selection.end.par() &&
797 selection.start.pos() == selection.end.pos())
798 selection.set(false);
800 if (inset_owner && (selection.set() || lsel))
801 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
805 string const LyXText::selectionAsString(Buffer const * buffer,
808 if (!selection.set()) return string();
810 // should be const ...
811 ParagraphList::iterator startpit = selection.start.par();
812 ParagraphList::iterator endpit = selection.end.par();
813 pos_type const startpos(selection.start.pos());
814 pos_type const endpos(selection.end.pos());
816 if (startpit == endpit) {
817 return startpit->asString(buffer, startpos, endpos, label);
822 // First paragraph in selection
823 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
825 // The paragraphs in between (if any)
826 ParagraphList::iterator pit = boost::next(startpit);
827 for (; pit != endpit; ++pit) {
828 result += pit->asString(buffer, 0, pit->size(), label) + "\n\n";
831 // Last paragraph in selection
832 result += endpit->asString(buffer, 0, endpos, label);
838 void LyXText::clearSelection()
840 selection.set(false);
841 selection.mark(false);
842 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
843 // reset this in the bv_owner!
844 if (bv_owner && bv_owner->text)
845 bv_owner->text->xsel_cache.set(false);
849 void LyXText::cursorHome()
851 setCursor(cursor.par(), cursor.row()->pos());
855 void LyXText::cursorEnd()
857 if (cursor.par()->empty())
860 RowList::iterator rit = cursor.row();
861 RowList::iterator next_rit = boost::next(rit);
862 ParagraphList::iterator pit = rit->par();
863 pos_type last_pos = lastPos(*this, rit);
865 if (next_rit == rows().end() || next_rit->par() != pit) {
869 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
874 setCursor(pit, last_pos);
878 void LyXText::cursorTop()
880 setCursor(ownerParagraphs().begin(), 0);
884 void LyXText::cursorBottom()
886 ParagraphList::iterator lastpit =
887 boost::prior(ownerParagraphs().end());
888 setCursor(lastpit, lastpit->size());
892 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
894 // If the mask is completely neutral, tell user
895 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
896 // Could only happen with user style
897 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
901 // Try implicit word selection
902 // If there is a change in the language the implicit word selection
904 LyXCursor resetCursor = cursor;
905 bool implicitSelection = (font.language() == ignore_language
906 && font.number() == LyXFont::IGNORE)
907 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
910 setFont(font, toggleall);
912 // Implicit selections are cleared afterwards
913 //and cursor is set to the original position.
914 if (implicitSelection) {
916 cursor = resetCursor;
917 setCursor(cursor.par(), cursor.pos());
918 selection.cursor = cursor;
921 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
925 string LyXText::getStringToIndex()
927 // Try implicit word selection
928 // If there is a change in the language the implicit word selection
930 LyXCursor const reset_cursor = cursor;
931 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
934 if (!selection.set())
935 bv()->owner()->message(_("Nothing to index!"));
936 else if (selection.start.par() != selection.end.par())
937 bv()->owner()->message(_("Cannot index more than one paragraph!"));
939 idxstring = selectionAsString(bv()->buffer(), false);
941 // Reset cursors to their original position.
942 cursor = reset_cursor;
943 setCursor(cursor.par(), cursor.pos());
944 selection.cursor = cursor;
946 // Clear the implicit selection.
947 if (implicitSelection)
954 // the DTP switches for paragraphs. LyX will store them in the first
955 // physicla paragraph. When a paragraph is broken, the top settings rest,
956 // the bottom settings are given to the new one. So I can make shure,
957 // they do not duplicate themself and you cannnot make dirty things with
960 void LyXText::setParagraph(bool line_top, bool line_bottom,
961 bool pagebreak_top, bool pagebreak_bottom,
962 VSpace const & space_top,
963 VSpace const & space_bottom,
964 Spacing const & spacing,
966 string const & labelwidthstring,
969 LyXCursor tmpcursor = cursor;
970 if (!selection.set()) {
971 selection.start = cursor;
972 selection.end = cursor;
975 // make sure that the depth behind the selection are restored, too
976 ParagraphList::iterator endpit = boost::next(selection.end.par());
977 ParagraphList::iterator undoendpit = endpit;
978 ParagraphList::iterator pars_end = ownerParagraphs().end();
980 if (endpit != pars_end && endpit->getDepth()) {
981 while (endpit != pars_end && endpit->getDepth()) {
985 } else if (endpit != pars_end) {
986 // because of parindents etc.
990 setUndo(bv(), Undo::EDIT, selection.start.par(),
991 boost::prior(undoendpit));
994 ParagraphList::iterator tmppit = selection.end.par();
996 while (tmppit != boost::prior(selection.start.par())) {
997 setCursor(tmppit, 0);
998 postPaint(cursor.y() - cursor.row()->baseline());
1000 ParagraphList::iterator pit = cursor.par();
1001 ParagraphParameters & params = pit->params();
1003 params.lineTop(line_top);
1004 params.lineBottom(line_bottom);
1005 params.pagebreakTop(pagebreak_top);
1006 params.pagebreakBottom(pagebreak_bottom);
1007 params.spaceTop(space_top);
1008 params.spaceBottom(space_bottom);
1009 params.spacing(spacing);
1010 // does the layout allow the new alignment?
1011 LyXLayout_ptr const & layout = pit->layout();
1013 if (align == LYX_ALIGN_LAYOUT)
1014 align = layout->align;
1015 if (align & layout->alignpossible) {
1016 if (align == layout->align)
1017 params.align(LYX_ALIGN_LAYOUT);
1019 params.align(align);
1021 pit->setLabelWidthString(labelwidthstring);
1022 params.noindent(noindent);
1023 tmppit = boost::prior(pit);
1026 redoParagraphs(selection.start, endpit);
1029 setCursor(selection.start.par(), selection.start.pos());
1030 selection.cursor = cursor;
1031 setCursor(selection.end.par(), selection.end.pos());
1033 setCursor(tmpcursor.par(), tmpcursor.pos());
1035 bv()->updateInset(inset_owner);
1039 // set the counter of a paragraph. This includes the labels
1040 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1042 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1043 LyXLayout_ptr const & layout = pit->layout();
1045 if (pit != ownerParagraphs().begin()) {
1047 pit->params().appendix(boost::prior(pit)->params().appendix());
1048 if (!pit->params().appendix() &&
1049 pit->params().startOfAppendix()) {
1050 pit->params().appendix(true);
1051 textclass.counters().reset();
1053 pit->enumdepth = boost::prior(pit)->enumdepth;
1054 pit->itemdepth = boost::prior(pit)->itemdepth;
1056 pit->params().appendix(pit->params().startOfAppendix());
1061 /* Maybe we have to increment the enumeration depth.
1062 * BUT, enumeration in a footnote is considered in isolation from its
1063 * surrounding paragraph so don't increment if this is the
1064 * first line of the footnote
1065 * AND, bibliographies can't have their depth changed ie. they
1066 * are always of depth 0
1068 if (pit != ownerParagraphs().begin()
1069 && boost::prior(pit)->getDepth() < pit->getDepth()
1070 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1071 && pit->enumdepth < 3
1072 && layout->labeltype != LABEL_BIBLIO) {
1076 // Maybe we have to decrement the enumeration depth, see note above
1077 if (pit != ownerParagraphs().begin()
1078 && boost::prior(pit)->getDepth() > pit->getDepth()
1079 && layout->labeltype != LABEL_BIBLIO) {
1080 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1081 pit->getDepth())->enumdepth;
1084 if (!pit->params().labelString().empty()) {
1085 pit->params().labelString(string());
1088 if (layout->margintype == MARGIN_MANUAL) {
1089 if (pit->params().labelWidthString().empty()) {
1090 pit->setLabelWidthString(layout->labelstring());
1093 pit->setLabelWidthString(string());
1096 // is it a layout that has an automatic label?
1097 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1098 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1102 if (i >= 0 && i <= buf->params.secnumdepth) {
1106 textclass.counters().step(layout->latexname());
1108 // Is there a label? Useful for Chapter layout
1109 if (!pit->params().appendix()) {
1110 s << buf->B_(layout->labelstring());
1112 s << buf->B_(layout->labelstring_appendix());
1115 // Use of an integer is here less than elegant. For now.
1116 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1117 if (!pit->params().appendix()) {
1118 numbertype = "sectioning";
1120 numbertype = "appendix";
1121 if (pit->isRightToLeftPar(buf->params))
1122 langtype = "hebrew";
1128 << textclass.counters()
1129 .numberLabel(layout->latexname(),
1130 numbertype, langtype, head);
1132 pit->params().labelString(STRCONV(s.str()));
1134 // reset enum counters
1135 textclass.counters().reset("enum");
1136 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1137 textclass.counters().reset("enum");
1138 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1140 // Yes I know this is a really, really! bad solution
1142 string enumcounter("enum");
1144 switch (pit->enumdepth) {
1153 enumcounter += "iv";
1156 // not a valid enumdepth...
1160 textclass.counters().step(enumcounter);
1162 s << textclass.counters()
1163 .numberLabel(enumcounter, "enumeration");
1164 pit->params().labelString(STRCONV(s.str()));
1166 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1167 textclass.counters().step("bibitem");
1168 int number = textclass.counters().value("bibitem");
1169 if (pit->bibitem()) {
1170 pit->bibitem()->setCounter(number);
1171 pit->params().labelString(layout->labelstring());
1173 // In biblio should't be following counters but...
1175 string s = buf->B_(layout->labelstring());
1177 // the caption hack:
1178 if (layout->labeltype == LABEL_SENSITIVE) {
1179 ParagraphList::iterator tmppit = pit;
1182 while (tmppit != ownerParagraphs().end() &&
1184 // the single '=' is intended below
1185 && (in = tmppit->inInset()->owner())) {
1186 if (in->lyxCode() == Inset::FLOAT_CODE ||
1187 in->lyxCode() == Inset::WRAP_CODE) {
1191 tmppit = std::find(ownerParagraphs().begin(), ownerParagraphs().end(), *in->parOwner());
1198 if (in->lyxCode() == Inset::FLOAT_CODE)
1199 type = static_cast<InsetFloat*>(in)->params().type;
1200 else if (in->lyxCode() == Inset::WRAP_CODE)
1201 type = static_cast<InsetWrap*>(in)->params().type;
1205 Floating const & fl = textclass.floats().getType(type);
1207 textclass.counters().step(fl.type());
1209 // Doesn't work... yet.
1210 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1212 // par->SetLayout(0);
1213 // s = layout->labelstring;
1214 s = _("Senseless: ");
1217 pit->params().labelString(s);
1219 // reset the enumeration counter. They are always reset
1220 // when there is any other layout between
1221 // Just fall-through between the cases so that all
1222 // enum counters deeper than enumdepth is also reset.
1223 switch (pit->enumdepth) {
1225 textclass.counters().reset("enumi");
1227 textclass.counters().reset("enumii");
1229 textclass.counters().reset("enumiii");
1231 textclass.counters().reset("enumiv");
1237 // Updates all counters. Paragraphs with changed label string will be rebroken
1238 void LyXText::updateCounters()
1240 RowList::iterator rowit = rows().begin();
1241 ParagraphList::iterator pit = rowit->par();
1243 // CHECK if this is really needed. (Lgb)
1244 bv()->buffer()->params.getLyXTextClass().counters().reset();
1246 for (; pit != ownerParagraphs().end(); ++pit) {
1247 while (rowit->par() != pit)
1250 string const oldLabel = pit->params().labelString();
1252 size_t maxdepth = 0;
1253 if (pit != ownerParagraphs().begin())
1254 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1256 if (pit->params().depth() > maxdepth)
1257 pit->params().depth(maxdepth);
1259 // setCounter can potentially change the labelString.
1260 setCounter(bv()->buffer(), pit);
1262 string const & newLabel = pit->params().labelString();
1264 if (oldLabel.empty() && !newLabel.empty()) {
1265 removeParagraph(rowit);
1266 appendParagraph(rowit);
1272 void LyXText::insertInset(Inset * inset)
1274 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1276 setUndo(bv(), Undo::FINISH, cursor.par());
1278 cursor.par()->insertInset(cursor.pos(), inset);
1279 // Just to rebreak and refresh correctly.
1280 // The character will not be inserted a second time
1281 insertChar(Paragraph::META_INSET);
1282 // If we enter a highly editable inset the cursor should be to before
1283 // the inset. This couldn't happen before as Undo was not handled inside
1284 // inset now after the Undo LyX tries to call inset->Edit(...) again
1285 // and cannot do this as the cursor is behind the inset and GetInset
1286 // does not return the inset!
1287 if (isHighlyEditableInset(inset)) {
1294 void LyXText::cutSelection(bool doclear, bool realcut)
1296 // Stuff what we got on the clipboard. Even if there is no selection.
1298 // There is a problem with having the stuffing here in that the
1299 // larger the selection the slower LyX will get. This can be
1300 // solved by running the line below only when the selection has
1301 // finished. The solution used currently just works, to make it
1302 // faster we need to be more clever and probably also have more
1303 // calls to stuffClipboard. (Lgb)
1304 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1306 // This doesn't make sense, if there is no selection
1307 if (!selection.set())
1310 // OK, we have a selection. This is always between selection.start
1311 // and selection.end
1313 // make sure that the depth behind the selection are restored, too
1314 ParagraphList::iterator endpit = boost::next(selection.end.par());
1315 ParagraphList::iterator undoendpit = endpit;
1316 ParagraphList::iterator pars_end = ownerParagraphs().end();
1318 if (endpit != pars_end && endpit->getDepth()) {
1319 while (endpit != pars_end && endpit->getDepth()) {
1321 undoendpit = endpit;
1323 } else if (endpit != pars_end) {
1324 // because of parindents etc.
1328 setUndo(bv(), Undo::DELETE, selection.start.par(),
1329 boost::prior(undoendpit));
1332 endpit = selection.end.par();
1333 int endpos = selection.end.pos();
1335 boost::tie(endpit, endpos) = realcut ?
1336 CutAndPaste::cutSelection(ownerParagraphs(),
1337 selection.start.par(), endpit,
1338 selection.start.pos(), endpos,
1339 bv()->buffer()->params.textclass,
1341 : CutAndPaste::eraseSelection(ownerParagraphs(),
1342 selection.start.par(), endpit,
1343 selection.start.pos(), endpos,
1345 // sometimes necessary
1347 selection.start.par()->stripLeadingSpaces();
1349 redoParagraphs(selection.start, boost::next(endpit));
1350 #warning FIXME latent bug
1351 // endpit will be invalidated on redoParagraphs once ParagraphList
1352 // becomes a std::list? There are maybe other places on which this
1353 // can happend? (Ab)
1354 // cutSelection can invalidate the cursor so we need to set
1356 // we prefer the end for when tracking changes
1360 // need a valid cursor. (Lgb)
1363 setCursor(cursor.par(), cursor.pos());
1364 selection.cursor = cursor;
1369 void LyXText::copySelection()
1371 // stuff the selection onto the X clipboard, from an explicit copy request
1372 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1374 // this doesnt make sense, if there is no selection
1375 if (!selection.set())
1378 // ok we have a selection. This is always between selection.start
1379 // and sel_end cursor
1381 // copy behind a space if there is one
1382 while (selection.start.par()->size() > selection.start.pos()
1383 && selection.start.par()->isLineSeparator(selection.start.pos())
1384 && (selection.start.par() != selection.end.par()
1385 || selection.start.pos() < selection.end.pos()))
1386 selection.start.pos(selection.start.pos() + 1);
1388 CutAndPaste::copySelection(selection.start.par(),
1389 selection.end.par(),
1390 selection.start.pos(), selection.end.pos(),
1391 bv()->buffer()->params.textclass);
1395 void LyXText::pasteSelection()
1397 // this does not make sense, if there is nothing to paste
1398 if (!CutAndPaste::checkPastePossible())
1401 setUndo(bv(), Undo::INSERT, cursor.par());
1403 ParagraphList::iterator endpit;
1408 boost::tie(ppp, endpit) =
1409 CutAndPaste::pasteSelection(ownerParagraphs(),
1410 cursor.par(), cursor.pos(),
1411 bv()->buffer()->params.textclass,
1413 bv()->setErrorList(el);
1414 bv()->showErrorList(_("Paste"));
1416 redoParagraphs(cursor, endpit);
1418 setCursor(cursor.par(), cursor.pos());
1421 selection.cursor = cursor;
1422 setCursor(ppp.first, ppp.second);
1428 void LyXText::setSelectionRange(lyx::pos_type length)
1433 selection.cursor = cursor;
1440 // simple replacing. The font of the first selected character is used
1441 void LyXText::replaceSelectionWithString(string const & str)
1443 setCursorParUndo(bv());
1446 if (!selection.set()) { // create a dummy selection
1447 selection.end = cursor;
1448 selection.start = cursor;
1451 // Get font setting before we cut
1452 pos_type pos = selection.end.pos();
1453 LyXFont const font = selection.start.par()
1454 ->getFontSettings(bv()->buffer()->params,
1455 selection.start.pos());
1457 // Insert the new string
1458 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1459 selection.end.par()->insertChar(pos, (*cit), font);
1463 // Cut the selection
1464 cutSelection(true, false);
1470 // needed to insert the selection
1471 void LyXText::insertStringAsLines(string const & str)
1473 ParagraphList::iterator pit = cursor.par();
1474 pos_type pos = cursor.pos();
1475 ParagraphList::iterator endpit = boost::next(cursor.par());
1477 setCursorParUndo(bv());
1479 // only to be sure, should not be neccessary
1482 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1484 redoParagraphs(cursor, endpit);
1485 setCursor(cursor.par(), cursor.pos());
1486 selection.cursor = cursor;
1487 setCursor(pit, pos);
1492 // turns double-CR to single CR, others where converted into one
1493 // blank. Then InsertStringAsLines is called
1494 void LyXText::insertStringAsParagraphs(string const & str)
1496 string linestr(str);
1497 bool newline_inserted = false;
1498 for (string::size_type i = 0; i < linestr.length(); ++i) {
1499 if (linestr[i] == '\n') {
1500 if (newline_inserted) {
1501 // we know that \r will be ignored by
1502 // InsertStringA. Of course, it is a dirty
1503 // trick, but it works...
1504 linestr[i - 1] = '\r';
1508 newline_inserted = true;
1510 } else if (IsPrintable(linestr[i])) {
1511 newline_inserted = false;
1514 insertStringAsLines(linestr);
1518 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1520 LyXCursor tmpcursor;
1524 RowList::iterator row = getRow(pit, pos, y);
1525 RowList::iterator beg = rows().begin();
1527 // is there a break one row above
1529 && boost::prior(row)->par() == row->par()) {
1530 z = rowBreakPoint(*boost::prior(row));
1531 if (z >= row->pos()) {
1532 // set the dimensions of the row above
1533 y -= boost::prior(row)->height();
1536 breakAgain(boost::prior(row));
1538 // set the cursor again. Otherwise
1539 // dangling pointers are possible
1540 setCursor(cursor.par(), cursor.pos(),
1541 false, cursor.boundary());
1542 selection.cursor = cursor;
1547 int const tmpheight = row->height();
1548 pos_type const tmplast = lastPos(*this, row);
1551 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1552 postRowPaint(row, y);
1557 // check the special right address boxes
1558 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1565 redoDrawingOfParagraph(tmpcursor);
1568 // set the cursor again. Otherwise dangling pointers are possible
1569 // also set the selection
1571 if (selection.set()) {
1573 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1574 false, selection.cursor.boundary());
1575 selection.cursor = cursor;
1576 setCursorIntern(selection.start.par(),
1577 selection.start.pos(),
1578 false, selection.start.boundary());
1579 selection.start = cursor;
1580 setCursorIntern(selection.end.par(),
1581 selection.end.pos(),
1582 false, selection.end.boundary());
1583 selection.end = cursor;
1584 setCursorIntern(last_sel_cursor.par(),
1585 last_sel_cursor.pos(),
1586 false, last_sel_cursor.boundary());
1587 last_sel_cursor = cursor;
1590 setCursorIntern(cursor.par(), cursor.pos(),
1591 false, cursor.boundary());
1595 // returns false if inset wasn't found
1596 bool LyXText::updateInset(Inset * inset)
1598 // first check the current paragraph
1599 int pos = cursor.par()->getPositionOfInset(inset);
1601 checkParagraph(cursor.par(), pos);
1605 // check every paragraph
1607 ParagraphList::iterator par = ownerParagraphs().begin();
1608 ParagraphList::iterator end = ownerParagraphs().end();
1609 for (; par != end; ++par) {
1610 pos = par->getPositionOfInset(inset);
1612 checkParagraph(par, pos);
1621 bool LyXText::setCursor(ParagraphList::iterator pit,
1623 bool setfont, bool boundary)
1625 LyXCursor old_cursor = cursor;
1626 setCursorIntern(pit, pos, setfont, boundary);
1627 return deleteEmptyParagraphMechanism(old_cursor);
1631 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1632 pos_type pos, bool boundary)
1634 lyx::Assert(pit != ownerParagraphs().end());
1638 cur.boundary(boundary);
1640 // get the cursor y position in text
1642 RowList::iterator row = getRow(pit, pos, y);
1643 RowList::iterator beg = rows().begin();
1645 RowList::iterator old_row = row;
1647 // if we are before the first char of this row and are still in the
1648 // same paragraph and there is a previous row then put the cursor on
1649 // the end of the previous row
1650 cur.iy(y + row->baseline());
1653 boost::prior(row)->par() == row->par() &&
1654 pos < pit->size() &&
1655 pit->getChar(pos) == Paragraph::META_INSET) {
1656 Inset * ins = pit->getInset(pos);
1657 if (ins && (ins->needFullRow() || ins->display())) {
1664 // y is now the beginning of the cursor row
1665 y += row->baseline();
1666 // y is now the cursor baseline
1669 pos_type last = lastPrintablePos(*this, old_row);
1671 // None of these should happen, but we're scaredy-cats
1672 if (pos > pit->size()) {
1673 lyxerr << "dont like 1 please report" << endl;
1676 } else if (pos > last + 1) {
1677 lyxerr << "dont like 2 please report" << endl;
1678 // This shouldn't happen.
1681 } else if (pos < row->pos()) {
1682 lyxerr << "dont like 3 please report" << endl;
1687 // now get the cursors x position
1688 float x = getCursorX(row, pos, last, boundary);
1691 if (old_row != row) {
1692 x = getCursorX(old_row, pos, last, boundary);
1696 /* We take out this for the time being because 1) the redraw code is not
1697 prepared to this yet and 2) because some good policy has yet to be decided
1698 while editting: for instance how to act on rows being created/deleted
1702 //if the cursor is in a visible row, anchor to it
1704 if (topy < y && y < topy + bv()->workHeight())
1710 float LyXText::getCursorX(RowList::iterator rit,
1711 pos_type pos, pos_type last, bool boundary) const
1713 pos_type cursor_vpos = 0;
1715 float fill_separator;
1717 float fill_label_hfill;
1718 // This call HAS to be here because of the BidiTables!!!
1719 prepareToPrint(rit, x, fill_separator, fill_hfill,
1722 ParagraphList::iterator rit_par = rit->par();
1723 pos_type const rit_pos = rit->pos();
1726 cursor_vpos = rit_pos;
1727 else if (pos > last && !boundary)
1728 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1729 ? rit_pos : last + 1;
1730 else if (pos > rit_pos && (pos > last || boundary))
1731 /// Place cursor after char at (logical) position pos - 1
1732 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1733 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1735 /// Place cursor before char at (logical) position pos
1736 cursor_vpos = (bidi_level(pos) % 2 == 0)
1737 ? log2vis(pos) : log2vis(pos) + 1;
1739 pos_type body_pos = rit_par->beginningOfBody();
1740 if ((body_pos > 0) &&
1741 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1744 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1745 pos_type pos = vis2log(vpos);
1746 if (body_pos > 0 && pos == body_pos - 1) {
1747 x += fill_label_hfill +
1748 font_metrics::width(
1749 rit_par->layout()->labelsep,
1750 getLabelFont(bv()->buffer(), rit_par));
1751 if (rit_par->isLineSeparator(body_pos - 1))
1752 x -= singleWidth(rit_par, body_pos - 1);
1755 if (hfillExpansion(*this, rit, pos)) {
1756 x += singleWidth(rit_par, pos);
1757 if (pos >= body_pos)
1760 x += fill_label_hfill;
1761 } else if (rit_par->isSeparator(pos)) {
1762 x += singleWidth(rit_par, pos);
1763 if (pos >= body_pos)
1764 x += fill_separator;
1766 x += singleWidth(rit_par, pos);
1772 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1773 pos_type pos, bool setfont, bool boundary)
1775 UpdatableInset * it = pit->inInset();
1777 if (it != inset_owner) {
1778 lyxerr[Debug::INSETS] << "InsetText is " << it
1780 << "inset_owner is "
1781 << inset_owner << endl;
1782 #ifdef WITH_WARNINGS
1783 #warning I believe this code is wrong. (Lgb)
1784 #warning Jürgen, have a look at this. (Lgb)
1785 #warning Hmmm, I guess you are right but we
1786 #warning should verify when this is needed
1788 // Jürgen, would you like to have a look?
1789 // I guess we need to move the outer cursor
1790 // and open and lock the inset (bla bla bla)
1791 // stuff I don't know... so can you have a look?
1793 // I moved the lyxerr stuff in here so we can see if
1794 // this is actually really needed and where!
1796 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1801 setCursor(cursor, pit, pos, boundary);
1807 void LyXText::setCurrentFont()
1809 pos_type pos = cursor.pos();
1810 ParagraphList::iterator pit = cursor.par();
1812 if (cursor.boundary() && pos > 0)
1816 if (pos == pit->size())
1818 else // potentional bug... BUG (Lgb)
1819 if (pit->isSeparator(pos)) {
1820 if (pos > cursor.row()->pos() &&
1821 bidi_level(pos) % 2 ==
1822 bidi_level(pos - 1) % 2)
1824 else if (pos + 1 < pit->size())
1830 pit->getFontSettings(bv()->buffer()->params, pos);
1831 real_current_font = getFont(bv()->buffer(), pit, pos);
1833 if (cursor.pos() == pit->size() &&
1834 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1835 !cursor.boundary()) {
1836 Language const * lang =
1837 pit->getParLanguage(bv()->buffer()->params);
1838 current_font.setLanguage(lang);
1839 current_font.setNumber(LyXFont::OFF);
1840 real_current_font.setLanguage(lang);
1841 real_current_font.setNumber(LyXFont::OFF);
1846 // returns the column near the specified x-coordinate of the row
1847 // x is set to the real beginning of this column
1849 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1852 float fill_separator;
1854 float fill_label_hfill;
1856 prepareToPrint(rit, tmpx, fill_separator,
1857 fill_hfill, fill_label_hfill);
1859 pos_type vc = rit->pos();
1860 pos_type last = lastPrintablePos(*this, rit);
1863 ParagraphList::iterator rit_par = rit->par();
1864 LyXLayout_ptr const & layout = rit->par()->layout();
1866 bool left_side = false;
1868 pos_type body_pos = rit_par->beginningOfBody();
1869 float last_tmpx = tmpx;
1872 (body_pos - 1 > last ||
1873 !rit_par->isLineSeparator(body_pos - 1)))
1876 // check for empty row
1877 if (!rit_par->size()) {
1882 while (vc <= last && tmpx <= x) {
1885 if (body_pos > 0 && c == body_pos - 1) {
1886 tmpx += fill_label_hfill +
1887 font_metrics::width(layout->labelsep,
1888 getLabelFont(bv()->buffer(), rit_par));
1889 if (rit_par->isLineSeparator(body_pos - 1))
1890 tmpx -= singleWidth(rit_par, body_pos - 1);
1893 if (hfillExpansion(*this, rit, c)) {
1894 tmpx += singleWidth(rit_par, c);
1898 tmpx += fill_label_hfill;
1899 } else if (rit_par->isSeparator(c)) {
1900 tmpx += singleWidth(rit_par, c);
1902 tmpx+= fill_separator;
1904 tmpx += singleWidth(rit_par, c);
1909 if ((tmpx + last_tmpx) / 2 > x) {
1914 if (vc > last + 1) // This shouldn't happen.
1918 // This (rtl_support test) is not needed, but gives
1919 // some speedup if rtl_support=false
1920 bool const lastrow = lyxrc.rtl_support &&
1921 (boost::next(rit) == rowlist_.end() ||
1922 boost::next(rit)->par() != rit_par);
1923 // If lastrow is false, we don't need to compute
1924 // the value of rtl.
1925 bool const rtl = (lastrow)
1926 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1929 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1930 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1932 else if (vc == rit->pos()) {
1934 if (bidi_level(c) % 2 == 1)
1937 c = vis2log(vc - 1);
1938 bool const rtl = (bidi_level(c) % 2 == 1);
1939 if (left_side == rtl) {
1941 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1945 if (rit->pos() <= last && c > last
1946 && rit_par->isNewline(last)) {
1947 if (bidi_level(last) % 2 == 0)
1948 tmpx -= singleWidth(rit_par, last);
1950 tmpx += singleWidth(rit_par, last);
1960 void LyXText::setCursorFromCoordinates(int x, int y)
1962 LyXCursor old_cursor = cursor;
1964 setCursorFromCoordinates(cursor, x, y);
1966 deleteEmptyParagraphMechanism(old_cursor);
1973 * return true if the cursor given is at the end of a row,
1974 * and the next row is filled by an inset that spans an entire
1977 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur) {
1978 RowList::iterator row = cur.row();
1979 if (boost::next(row) == lt.rows().end())
1981 Row const & next = *boost::next(row);
1983 if (next.pos() != cur.pos() || next.par() != cur.par())
1986 if (cur.pos() == cur.par()->size()
1987 || !cur.par()->isInset(cur.pos()))
1989 Inset const * inset = cur.par()->getInset(cur.pos());
1990 if (inset->needFullRow() || inset->display())
1997 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1999 // Get the row first.
2001 RowList::iterator row = getRowNearY(y);
2003 pos_type const column = getColumnNearX(row, x, bound);
2004 cur.par(row->par());
2005 cur.pos(row->pos() + column);
2007 cur.y(y + row->baseline());
2010 if (beforeFullRowInset(*this, cur)) {
2011 pos_type last = lastPrintablePos(*this, row);
2012 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2014 cur.iy(y + row->height() + boost::next(row)->baseline());
2015 cur.irow(boost::next(row));
2021 cur.boundary(bound);
2025 void LyXText::cursorLeft(bool internal)
2027 if (cursor.pos() > 0) {
2028 bool boundary = cursor.boundary();
2029 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2030 if (!internal && !boundary &&
2031 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2032 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2033 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2034 ParagraphList::iterator pit = boost::prior(cursor.par());
2035 setCursor(pit, pit->size());
2040 void LyXText::cursorRight(bool internal)
2042 bool const at_end = (cursor.pos() == cursor.par()->size());
2043 bool const at_newline = !at_end &&
2044 cursor.par()->isNewline(cursor.pos());
2046 if (!internal && cursor.boundary() && !at_newline)
2047 setCursor(cursor.par(), cursor.pos(), true, false);
2049 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2051 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2052 setCursor(cursor.par(), cursor.pos(), true, true);
2053 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2054 setCursor(boost::next(cursor.par()), 0);
2058 void LyXText::cursorUp(bool selecting)
2061 int x = cursor.x_fix();
2062 int y = cursor.y() - cursor.row()->baseline() - 1;
2063 setCursorFromCoordinates(x, y);
2066 int y1 = cursor.iy() - topy;
2069 Inset * inset_hit = checkInsetHit(x, y1);
2070 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2071 inset_hit->localDispatch(
2072 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
2076 setCursorFromCoordinates(bv(), cursor.x_fix(),
2077 cursor.y() - cursor.row()->baseline() - 1);
2082 void LyXText::cursorDown(bool selecting)
2085 int x = cursor.x_fix();
2086 int y = cursor.y() - cursor.row()->baseline() +
2087 cursor.row()->height() + 1;
2088 setCursorFromCoordinates(x, y);
2089 if (!selecting && cursor.row() == cursor.irow()) {
2091 int y1 = cursor.iy() - topy;
2094 Inset * inset_hit = checkInsetHit(x, y1);
2095 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2096 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
2097 inset_hit->localDispatch(cmd);
2101 setCursorFromCoordinates(bv(), cursor.x_fix(),
2102 cursor.y() - cursor.row()->baseline()
2103 + cursor.row()->height() + 1);
2108 void LyXText::cursorUpParagraph()
2110 if (cursor.pos() > 0) {
2111 setCursor(cursor.par(), 0);
2113 else if (cursor.par() != ownerParagraphs().begin()) {
2114 setCursor(boost::prior(cursor.par()), 0);
2119 void LyXText::cursorDownParagraph()
2121 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2122 setCursor(boost::next(cursor.par()), 0);
2124 setCursor(cursor.par(), cursor.par()->size());
2128 // fix the cursor `cur' after a characters has been deleted at `where'
2129 // position. Called by deleteEmptyParagraphMechanism
2130 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2131 LyXCursor const & where)
2133 // if cursor is not in the paragraph where the delete occured,
2135 if (cur.par() != where.par())
2138 // if cursor position is after the place where the delete occured,
2140 if (cur.pos() > where.pos())
2141 cur.pos(cur.pos()-1);
2143 // check also if we don't want to set the cursor on a spot behind the
2144 // pagragraph because we erased the last character.
2145 if (cur.pos() > cur.par()->size())
2146 cur.pos(cur.par()->size());
2148 // recompute row et al. for this cursor
2149 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2153 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2155 // Would be wrong to delete anything if we have a selection.
2156 if (selection.set())
2159 // We allow all kinds of "mumbo-jumbo" when freespacing.
2160 if (old_cursor.par()->layout()->free_spacing
2161 || old_cursor.par()->isFreeSpacing()) {
2165 /* Ok I'll put some comments here about what is missing.
2166 I have fixed BackSpace (and thus Delete) to not delete
2167 double-spaces automagically. I have also changed Cut,
2168 Copy and Paste to hopefully do some sensible things.
2169 There are still some small problems that can lead to
2170 double spaces stored in the document file or space at
2171 the beginning of paragraphs. This happens if you have
2172 the cursor betwenn to spaces and then save. Or if you
2173 cut and paste and the selection have a space at the
2174 beginning and then save right after the paste. I am
2175 sure none of these are very hard to fix, but I will
2176 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2177 that I can get some feedback. (Lgb)
2180 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2181 // delete the LineSeparator.
2184 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2185 // delete the LineSeparator.
2188 // If the pos around the old_cursor were spaces, delete one of them.
2189 if (old_cursor.par() != cursor.par()
2190 || old_cursor.pos() != cursor.pos()) {
2191 // Only if the cursor has really moved
2193 if (old_cursor.pos() > 0
2194 && old_cursor.pos() < old_cursor.par()->size()
2195 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2196 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2197 old_cursor.par()->erase(old_cursor.pos() - 1);
2198 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2200 #ifdef WITH_WARNINGS
2201 #warning This will not work anymore when we have multiple views of the same buffer
2202 // In this case, we will have to correct also the cursors held by
2203 // other bufferviews. It will probably be easier to do that in a more
2204 // automated way in LyXCursor code. (JMarc 26/09/2001)
2206 // correct all cursors held by the LyXText
2207 fixCursorAfterDelete(cursor, old_cursor);
2208 fixCursorAfterDelete(selection.cursor,
2210 fixCursorAfterDelete(selection.start,
2212 fixCursorAfterDelete(selection.end, old_cursor);
2213 fixCursorAfterDelete(last_sel_cursor,
2215 fixCursorAfterDelete(toggle_cursor, old_cursor);
2216 fixCursorAfterDelete(toggle_end_cursor,
2222 // don't delete anything if this is the ONLY paragraph!
2223 if (ownerParagraphs().size() == 1)
2226 // Do not delete empty paragraphs with keepempty set.
2227 if (old_cursor.par()->layout()->keepempty)
2230 // only do our magic if we changed paragraph
2231 if (old_cursor.par() == cursor.par())
2234 // record if we have deleted a paragraph
2235 // we can't possibly have deleted a paragraph before this point
2236 bool deleted = false;
2238 if (old_cursor.par()->empty() ||
2239 (old_cursor.par()->size() == 1 &&
2240 old_cursor.par()->isLineSeparator(0))) {
2241 // ok, we will delete anything
2242 LyXCursor tmpcursor;
2246 bool selection_position_was_oldcursor_position = (
2247 selection.cursor.par() == old_cursor.par()
2248 && selection.cursor.pos() == old_cursor.pos());
2250 if (old_cursor.row() != rows().begin()) {
2252 prevrow = boost::prior(old_cursor.row());
2253 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2255 cursor = old_cursor; // that undo can restore the right cursor position
2256 #warning FIXME. --end() iterator is usable here
2257 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2258 while (endpit != ownerParagraphs().end() &&
2259 endpit->getDepth()) {
2263 setUndo(bv(), Undo::DELETE, old_cursor.par(),
2264 boost::prior(endpit));
2268 removeRow(old_cursor.row());
2270 ownerParagraphs().erase(old_cursor.par());
2272 /* Breakagain the next par. Needed because of
2273 * the parindent that can occur or dissappear.
2274 * The next row can change its height, if
2275 * there is another layout before */
2276 if (boost::next(prevrow) != rows().end()) {
2277 breakAgain(boost::next(prevrow));
2280 setHeightOfRow(prevrow);
2282 RowList::iterator nextrow = boost::next(old_cursor.row());
2283 const_cast<LyXText *>(this)->postPaint(
2284 old_cursor.y() - old_cursor.row()->baseline());
2287 cursor = old_cursor; // that undo can restore the right cursor position
2288 #warning FIXME. --end() iterator is usable here
2289 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2290 while (endpit != ownerParagraphs().end() &&
2291 endpit->getDepth()) {
2295 setUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2299 removeRow(old_cursor.row());
2301 ownerParagraphs().erase(old_cursor.par());
2303 /* Breakagain the next par. Needed because of
2304 the parindent that can occur or dissappear.
2305 The next row can change its height, if
2306 there is another layout before */
2307 if (nextrow != rows().end()) {
2308 breakAgain(nextrow);
2314 setCursorIntern(cursor.par(), cursor.pos());
2316 if (selection_position_was_oldcursor_position) {
2317 // correct selection
2318 selection.cursor = cursor;
2322 if (old_cursor.par()->stripLeadingSpaces()) {
2323 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2325 setCursorIntern(cursor.par(), cursor.pos());
2326 selection.cursor = cursor;
2333 ParagraphList & LyXText::ownerParagraphs() const
2336 return inset_owner->paragraphs;
2338 return bv_owner->buffer()->paragraphs;
2342 LyXText::refresh_status LyXText::refreshStatus() const
2344 return refresh_status_;
2348 void LyXText::clearPaint()
2350 refresh_status_ = REFRESH_NONE;
2351 refresh_row = rows().end();
2356 void LyXText::postPaint(int start_y)
2358 refresh_status old = refresh_status_;
2360 refresh_status_ = REFRESH_AREA;
2361 refresh_row = rows().end();
2363 if (old != REFRESH_NONE && refresh_y < start_y)
2366 refresh_y = start_y;
2371 // We are an inset's lyxtext. Tell the top-level lyxtext
2372 // it needs to update the row we're in.
2373 LyXText * t = bv()->text;
2374 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2378 // FIXME: we should probably remove this y parameter,
2379 // make refresh_y be 0, and use row->y etc.
2380 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2382 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2383 refresh_status_ = REFRESH_AREA;
2386 refresh_y = start_y;
2389 if (refresh_status_ == REFRESH_AREA)
2392 refresh_status_ = REFRESH_ROW;
2398 // We are an inset's lyxtext. Tell the top-level lyxtext
2399 // it needs to update the row we're in.
2400 LyXText * t = bv()->text;
2401 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2405 bool LyXText::isInInset() const
2407 // Sub-level has non-null bv owner and
2408 // non-null inset owner.
2409 return inset_owner != 0 && bv_owner != 0;
2413 int defaultRowHeight()
2415 LyXFont const font(LyXFont::ALL_SANE);
2416 return int(font_metrics::maxAscent(font)
2417 + font_metrics::maxDescent(font) * 1.5);