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"
23 #include "BufferView.h"
24 #include "CutAndPaste.h"
25 #include "frontends/Painter.h"
26 #include "frontends/font_metrics.h"
30 #include "FloatList.h"
32 #include "ParagraphParameters.h"
34 #include "lyxrow_funcs.h"
35 #include "paragraph_funcs.h"
37 #include "insets/insetbibitem.h"
38 #include "insets/insetenv.h"
39 #include "insets/insetfloat.h"
40 #include "insets/insetwrap.h"
42 #include "support/LAssert.h"
43 #include "support/textutils.h"
44 #include "support/lstrings.h"
46 #include <boost/tuple/tuple.hpp>
56 LyXText::LyXText(BufferView * bv)
57 : height(0), width(0), anchor_row_offset_(0),
58 inset_owner(0), the_locking_inset(0), bv_owner(bv)
60 anchor_row_ = rows().end();
61 need_break_row = rows().end();
62 refresh_row = rows().end();
68 LyXText::LyXText(BufferView * bv, InsetText * inset)
69 : height(0), width(0), anchor_row_offset_(0),
70 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
72 anchor_row_ = rows().end();
73 need_break_row = rows().end();
74 refresh_row = rows().end();
80 void LyXText::init(BufferView * bview, bool reinit)
84 need_break_row = rows().end();
88 } else if (!rowlist_.empty())
91 ParagraphList::iterator pit = ownerParagraphs().begin();
92 ParagraphList::iterator end = ownerParagraphs().end();
94 current_font = getFont(bview->buffer(), pit, 0);
96 for (; pit != end; ++pit) {
97 insertParagraph(pit, rowlist_.end());
99 setCursorIntern(rowlist_.begin()->par(), 0);
100 selection.cursor = cursor;
106 // Gets the fully instantiated font at a given position in a paragraph
107 // Basically the same routine as Paragraph::getFont() in paragraph.C.
108 // The difference is that this one is used for displaying, and thus we
109 // are allowed to make cosmetic improvements. For instance make footnotes
111 // If position is -1, we get the layout font of the paragraph.
112 // If position is -2, we get the font of the manual label of the paragraph.
113 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
116 lyx::Assert(pos >= 0);
118 LyXLayout_ptr const & layout = pit->layout();
120 // We specialize the 95% common case:
121 if (!pit->getDepth()) {
122 if (layout->labeltype == LABEL_MANUAL
123 && pos < pit->beginningOfBody()) {
125 LyXFont f = pit->getFontSettings(buf->params, pos);
127 pit->inInset()->getDrawFont(f);
128 return f.realize(layout->reslabelfont);
130 LyXFont f = pit->getFontSettings(buf->params, pos);
132 pit->inInset()->getDrawFont(f);
133 return f.realize(layout->resfont);
137 // The uncommon case need not be optimized as much
141 if (pos < pit->beginningOfBody()) {
143 layoutfont = layout->labelfont;
146 layoutfont = layout->font;
149 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
150 tmpfont.realize(layoutfont);
153 pit->inInset()->getDrawFont(tmpfont);
155 // Realize with the fonts of lesser depth.
156 tmpfont.realize(outerFont(pit, ownerParagraphs()));
158 return realizeFont(tmpfont, buf->params);
162 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
163 ParagraphList::iterator pit) const
165 LyXLayout_ptr const & layout = pit->layout();
167 if (!pit->getDepth()) {
168 return layout->resfont;
171 LyXFont font(layout->font);
172 // Realize with the fonts of lesser depth.
173 font.realize(outerFont(pit, ownerParagraphs()));
175 return realizeFont(font, buf->params);
179 LyXFont const LyXText::getLabelFont(Buffer const * buf,
180 ParagraphList::iterator pit) const
182 LyXLayout_ptr const & layout = pit->layout();
184 if (!pit->getDepth()) {
185 return layout->reslabelfont;
188 LyXFont font(layout->labelfont);
189 // Realize with the fonts of lesser depth.
190 font.realize(outerFont(pit, ownerParagraphs()));
192 return realizeFont(layout->labelfont, buf->params);
196 void LyXText::setCharFont(ParagraphList::iterator pit,
197 pos_type pos, LyXFont const & fnt,
200 Buffer const * buf = bv()->buffer();
201 LyXFont font = getFont(buf, pit, pos);
202 font.update(fnt, buf->params.language, toggleall);
203 // Let the insets convert their font
204 if (pit->isInset(pos)) {
205 Inset * inset = pit->getInset(pos);
206 if (isEditableInset(inset)) {
207 UpdatableInset * uinset =
208 static_cast<UpdatableInset *>(inset);
209 uinset->setFont(bv(), fnt, toggleall, true);
213 // Plug thru to version below:
214 setCharFont(buf, pit, pos, font);
218 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
219 pos_type pos, LyXFont const & fnt)
223 LyXTextClass const & tclass = buf->params.getLyXTextClass();
224 LyXLayout_ptr const & layout = pit->layout();
226 // Get concrete layout font to reduce against
229 if (pos < pit->beginningOfBody())
230 layoutfont = layout->labelfont;
232 layoutfont = layout->font;
234 // Realize against environment font information
235 if (pit->getDepth()) {
236 ParagraphList::iterator tp = pit;
237 while (!layoutfont.resolved() &&
238 tp != ownerParagraphs().end() &&
240 tp = outerHook(tp, ownerParagraphs());
241 if (tp != ownerParagraphs().end())
242 layoutfont.realize(tp->layout()->font);
246 layoutfont.realize(tclass.defaultfont());
248 // Now, reduce font against full layout font
249 font.reduce(layoutfont);
251 pit->setFont(pos, font);
255 // removes the row and reset the touched counters
256 void LyXText::removeRow(RowList::iterator rit)
258 /* FIXME: when we cache the bview, this should just
259 * become a postPaint(), I think */
260 if (refresh_row == rit) {
261 if (rit == rows().begin())
262 refresh_row = boost::next(rit);
264 refresh_row = boost::prior(rit);
266 // what about refresh_y
269 if (anchor_row_ == rit) {
270 if (rit != rows().begin()) {
271 anchor_row_ = boost::prior(rit);
272 anchor_row_offset_ += anchor_row_->height();
274 anchor_row_ = boost::next(rit);
275 anchor_row_offset_ -= rit->height();
279 // the text becomes smaller
280 height -= rit->height();
286 // remove all following rows of the paragraph of the specified row.
287 void LyXText::removeParagraph(RowList::iterator rit)
289 ParagraphList::iterator tmppit = rit->par();
292 while (rit != rows().end() && rit->par() == tmppit) {
293 RowList::iterator tmprit = boost::next(rit);
300 void LyXText::insertParagraph(ParagraphList::iterator pit,
301 RowList::iterator rowit)
303 // insert a new row, starting at position 0
305 RowList::iterator rit = rowlist_.insert(rowit, newrow);
307 // and now append the whole paragraph before the new row
308 appendParagraph(rit);
312 Inset * LyXText::getInset() const
314 ParagraphList::iterator pit = cursor.par();
315 pos_type const pos = cursor.pos();
317 if (pos < pit->size() && pit->isInset(pos)) {
318 return pit->getInset(pos);
324 void LyXText::toggleInset()
326 Inset * inset = getInset();
327 // is there an editable inset at cursor position?
328 if (!isEditableInset(inset)) {
329 // No, try to see if we are inside a collapsable inset
330 if (inset_owner && inset_owner->owner()
331 && inset_owner->owner()->isOpen()) {
332 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
333 inset_owner->owner()->close(bv());
334 bv()->getLyXText()->cursorRight(bv());
338 //bv()->owner()->message(inset->editMessage());
340 // do we want to keep this?? (JMarc)
341 if (!isHighlyEditableInset(inset))
342 setCursorParUndo(bv());
344 if (inset->isOpen()) {
350 bv()->updateInset(inset);
354 /* used in setlayout */
355 // Asger is not sure we want to do this...
356 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
359 LyXLayout_ptr const & layout = par.layout();
360 pos_type const psize = par.size();
363 for (pos_type pos = 0; pos < psize; ++pos) {
364 if (pos < par.beginningOfBody())
365 layoutfont = layout->labelfont;
367 layoutfont = layout->font;
369 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
370 tmpfont.reduce(layoutfont);
371 par.setFont(pos, tmpfont);
376 ParagraphList::iterator
377 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
378 LyXCursor & send_cur,
379 string const & layout)
381 ParagraphList::iterator endpit = boost::next(send_cur.par());
382 ParagraphList::iterator undoendpit = endpit;
383 ParagraphList::iterator pars_end = ownerParagraphs().end();
385 if (endpit != pars_end && endpit->getDepth()) {
386 while (endpit != pars_end && endpit->getDepth()) {
390 } else if (endpit != pars_end) {
391 // because of parindents etc.
395 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpit);
397 // ok we have a selection. This is always between sstart_cur
398 // and sel_end cursor
400 ParagraphList::iterator pit = sstart_cur.par();
401 ParagraphList::iterator epit = boost::next(send_cur.par());
403 LyXLayout_ptr const & lyxlayout =
404 bv()->buffer()->params.getLyXTextClass()[layout];
407 pit->applyLayout(lyxlayout);
408 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
409 ParagraphList::iterator fppit = pit;
410 fppit->params().spaceTop(lyxlayout->fill_top ?
411 VSpace(VSpace::VFILL)
412 : VSpace(VSpace::NONE));
413 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
414 VSpace(VSpace::VFILL)
415 : VSpace(VSpace::NONE));
416 if (lyxlayout->margintype == MARGIN_MANUAL)
417 pit->setLabelWidthString(lyxlayout->labelstring());
420 } while (pit != epit);
426 // set layout over selection and make a total rebreak of those paragraphs
427 void LyXText::setLayout(string const & layout)
429 LyXCursor tmpcursor = cursor; // store the current cursor
431 // if there is no selection just set the layout
432 // of the current paragraph
433 if (!selection.set()) {
434 selection.start = cursor; // dummy selection
435 selection.end = cursor;
438 // special handling of new environment insets
439 BufferParams const & params = bv()->buffer()->params;
440 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
441 if (lyxlayout->is_environment) {
442 // move everything in a new environment inset
443 lyxerr << "setting layout " << layout << endl;
444 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
445 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
446 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
447 Inset * inset = new InsetEnvironment(params, layout);
448 if (bv()->insertInset(inset)) {
450 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
457 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
458 selection.end, layout);
459 redoParagraphs(selection.start, endpit);
461 // we have to reset the selection, because the
462 // geometry could have changed
463 setCursor(selection.start.par(), selection.start.pos(), false);
464 selection.cursor = cursor;
465 setCursor(selection.end.par(), selection.end.pos(), false);
469 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
473 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
475 ParagraphList::iterator pit(cursor.par());
476 ParagraphList::iterator end(cursor.par());
477 ParagraphList::iterator start = pit;
479 if (selection.set()) {
480 pit = selection.start.par();
481 end = selection.end.par();
485 ParagraphList::iterator pastend = boost::next(end);
488 setUndo(bv(), Undo::EDIT, start, pastend);
490 bool changed = false;
492 int prev_after_depth = 0;
493 #warning parlist ... could be nicer ?
494 if (start != ownerParagraphs().begin()) {
495 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
499 int const depth = pit->params().depth();
500 if (type == bv_funcs::INC_DEPTH) {
501 if (depth < prev_after_depth
502 && pit->layout()->labeltype != LABEL_BIBLIO) {
505 pit->params().depth(depth + 1);
512 pit->params().depth(depth - 1);
515 prev_after_depth = pit->getMaxDepthAfter();
527 // Wow, redoParagraphs is stupid.
529 setCursor(tmpcursor, start, 0);
531 //redoParagraphs(tmpcursor, &(*pastend));
532 redoParagraphs(tmpcursor, pastend);
534 // We need to actually move the text->cursor. I don't
535 // understand why ...
538 // we have to reset the visual selection because the
539 // geometry could have changed
540 if (selection.set()) {
541 setCursor(selection.start.par(), selection.start.pos());
542 selection.cursor = cursor;
543 setCursor(selection.end.par(), selection.end.pos());
546 // this handles the counter labels, and also fixes up
547 // depth values for follow-on (child) paragraphs
551 setCursor(tmpcursor.par(), tmpcursor.pos());
557 // set font over selection and make a total rebreak of those paragraphs
558 void LyXText::setFont(LyXFont const & font, bool toggleall)
560 // if there is no selection just set the current_font
561 if (!selection.set()) {
562 // Determine basis font
564 if (cursor.pos() < cursor.par()->beginningOfBody()) {
565 layoutfont = getLabelFont(bv()->buffer(),
568 layoutfont = getLayoutFont(bv()->buffer(),
571 // Update current font
572 real_current_font.update(font,
573 bv()->buffer()->params.language,
576 // Reduce to implicit settings
577 current_font = real_current_font;
578 current_font.reduce(layoutfont);
579 // And resolve it completely
580 real_current_font.realize(layoutfont);
585 LyXCursor tmpcursor = cursor; // store the current cursor
587 // ok we have a selection. This is always between sel_start_cursor
588 // and sel_end cursor
590 setUndo(bv(), Undo::EDIT,
591 selection.start.par(), boost::next(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()
887 // This is how it should be:
888 // ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
889 ParagraphList::iterator lastpit = &ownerParagraphs().back();
890 int pos = lastpit->size();
891 setCursor(lastpit, pos);
895 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
897 // If the mask is completely neutral, tell user
898 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
899 // Could only happen with user style
900 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
904 // Try implicit word selection
905 // If there is a change in the language the implicit word selection
907 LyXCursor resetCursor = cursor;
908 bool implicitSelection = (font.language() == ignore_language
909 && font.number() == LyXFont::IGNORE)
910 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
913 setFont(font, toggleall);
915 // Implicit selections are cleared afterwards
916 //and cursor is set to the original position.
917 if (implicitSelection) {
919 cursor = resetCursor;
920 setCursor(cursor.par(), cursor.pos());
921 selection.cursor = cursor;
924 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
928 string LyXText::getStringToIndex()
930 // Try implicit word selection
931 // If there is a change in the language the implicit word selection
933 LyXCursor const reset_cursor = cursor;
934 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
937 if (!selection.set())
938 bv()->owner()->message(_("Nothing to index!"));
939 else if (selection.start.par() != selection.end.par())
940 bv()->owner()->message(_("Cannot index more than one paragraph!"));
942 idxstring = selectionAsString(bv()->buffer(), false);
944 // Reset cursors to their original position.
945 cursor = reset_cursor;
946 setCursor(cursor.par(), cursor.pos());
947 selection.cursor = cursor;
949 // Clear the implicit selection.
950 if (implicitSelection)
957 // the DTP switches for paragraphs. LyX will store them in the first
958 // physicla paragraph. When a paragraph is broken, the top settings rest,
959 // the bottom settings are given to the new one. So I can make shure,
960 // they do not duplicate themself and you cannnot make dirty things with
963 void LyXText::setParagraph(bool line_top, bool line_bottom,
964 bool pagebreak_top, bool pagebreak_bottom,
965 VSpace const & space_top,
966 VSpace const & space_bottom,
967 Spacing const & spacing,
969 string const & labelwidthstring,
972 LyXCursor tmpcursor = cursor;
973 if (!selection.set()) {
974 selection.start = cursor;
975 selection.end = cursor;
978 // make sure that the depth behind the selection are restored, too
979 ParagraphList::iterator endpit = boost::next(selection.end.par());
980 ParagraphList::iterator undoendpit = endpit;
981 ParagraphList::iterator pars_end = ownerParagraphs().end();
983 if (endpit != pars_end && endpit->getDepth()) {
984 while (endpit != pars_end && endpit->getDepth()) {
988 } else if (endpit != pars_end) {
989 // because of parindents etc.
993 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpit);
996 ParagraphList::iterator tmppit = selection.end.par();
998 while (tmppit != boost::prior(selection.start.par())) {
999 setCursor(tmppit, 0);
1000 postPaint(cursor.y() - cursor.row()->baseline());
1002 ParagraphList::iterator pit = cursor.par();
1003 ParagraphParameters & params = pit->params();
1005 params.lineTop(line_top);
1006 params.lineBottom(line_bottom);
1007 params.pagebreakTop(pagebreak_top);
1008 params.pagebreakBottom(pagebreak_bottom);
1009 params.spaceTop(space_top);
1010 params.spaceBottom(space_bottom);
1011 params.spacing(spacing);
1012 // does the layout allow the new alignment?
1013 LyXLayout_ptr const & layout = pit->layout();
1015 if (align == LYX_ALIGN_LAYOUT)
1016 align = layout->align;
1017 if (align & layout->alignpossible) {
1018 if (align == layout->align)
1019 params.align(LYX_ALIGN_LAYOUT);
1021 params.align(align);
1023 pit->setLabelWidthString(labelwidthstring);
1024 params.noindent(noindent);
1025 tmppit = boost::prior(pit);
1028 redoParagraphs(selection.start, endpit);
1031 setCursor(selection.start.par(), selection.start.pos());
1032 selection.cursor = cursor;
1033 setCursor(selection.end.par(), selection.end.pos());
1035 setCursor(tmpcursor.par(), tmpcursor.pos());
1037 bv()->updateInset(inset_owner);
1041 // set the counter of a paragraph. This includes the labels
1042 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1044 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1045 LyXLayout_ptr const & layout = pit->layout();
1047 if (pit != ownerParagraphs().begin()) {
1049 pit->params().appendix(boost::prior(pit)->params().appendix());
1050 if (!pit->params().appendix() &&
1051 pit->params().startOfAppendix()) {
1052 pit->params().appendix(true);
1053 textclass.counters().reset();
1055 pit->enumdepth = boost::prior(pit)->enumdepth;
1056 pit->itemdepth = boost::prior(pit)->itemdepth;
1058 pit->params().appendix(pit->params().startOfAppendix());
1063 /* Maybe we have to increment the enumeration depth.
1064 * BUT, enumeration in a footnote is considered in isolation from its
1065 * surrounding paragraph so don't increment if this is the
1066 * first line of the footnote
1067 * AND, bibliographies can't have their depth changed ie. they
1068 * are always of depth 0
1070 if (pit != ownerParagraphs().begin()
1071 && boost::prior(pit)->getDepth() < pit->getDepth()
1072 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1073 && pit->enumdepth < 3
1074 && layout->labeltype != LABEL_BIBLIO) {
1078 // Maybe we have to decrement the enumeration depth, see note above
1079 if (pit != ownerParagraphs().begin()
1080 && boost::prior(pit)->getDepth() > pit->getDepth()
1081 && layout->labeltype != LABEL_BIBLIO) {
1082 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1083 pit->getDepth())->enumdepth;
1086 if (!pit->params().labelString().empty()) {
1087 pit->params().labelString(string());
1090 if (layout->margintype == MARGIN_MANUAL) {
1091 if (pit->params().labelWidthString().empty()) {
1092 pit->setLabelWidthString(layout->labelstring());
1095 pit->setLabelWidthString(string());
1098 // is it a layout that has an automatic label?
1099 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1100 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1104 if (i >= 0 && i <= buf->params.secnumdepth) {
1108 textclass.counters().step(layout->latexname());
1110 // Is there a label? Useful for Chapter layout
1111 if (!pit->params().appendix()) {
1112 s << buf->B_(layout->labelstring());
1114 s << buf->B_(layout->labelstring_appendix());
1117 // Use of an integer is here less than elegant. For now.
1118 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1119 if (!pit->params().appendix()) {
1120 numbertype = "sectioning";
1122 numbertype = "appendix";
1123 if (pit->isRightToLeftPar(buf->params))
1124 langtype = "hebrew";
1130 << textclass.counters()
1131 .numberLabel(layout->latexname(),
1132 numbertype, langtype, head);
1134 pit->params().labelString(STRCONV(s.str()));
1136 // reset enum counters
1137 textclass.counters().reset("enum");
1138 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1139 textclass.counters().reset("enum");
1140 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1142 // Yes I know this is a really, really! bad solution
1144 string enumcounter("enum");
1146 switch (pit->enumdepth) {
1155 enumcounter += "iv";
1158 // not a valid enumdepth...
1162 textclass.counters().step(enumcounter);
1164 s << textclass.counters()
1165 .numberLabel(enumcounter, "enumeration");
1166 pit->params().labelString(STRCONV(s.str()));
1168 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1169 textclass.counters().step("bibitem");
1170 int number = textclass.counters().value("bibitem");
1171 if (pit->bibitem()) {
1172 pit->bibitem()->setCounter(number);
1173 pit->params().labelString(layout->labelstring());
1175 // In biblio should't be following counters but...
1177 string s = buf->B_(layout->labelstring());
1179 // the caption hack:
1180 if (layout->labeltype == LABEL_SENSITIVE) {
1181 ParagraphList::iterator tmppit = pit;
1184 while (tmppit != ownerParagraphs().end() &&
1186 // the single '=' is intended below
1187 && (in = tmppit->inInset()->owner())) {
1188 if (in->lyxCode() == Inset::FLOAT_CODE ||
1189 in->lyxCode() == Inset::WRAP_CODE) {
1193 tmppit = in->parOwner();
1200 if (in->lyxCode() == Inset::FLOAT_CODE)
1201 type = static_cast<InsetFloat*>(in)->params().type;
1202 else if (in->lyxCode() == Inset::WRAP_CODE)
1203 type = static_cast<InsetWrap*>(in)->params().type;
1207 Floating const & fl = textclass.floats().getType(type);
1209 textclass.counters().step(fl.type());
1211 // Doesn't work... yet.
1212 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1214 // par->SetLayout(0);
1215 // s = layout->labelstring;
1216 s = _("Senseless: ");
1219 pit->params().labelString(s);
1221 // reset the enumeration counter. They are always reset
1222 // when there is any other layout between
1223 // Just fall-through between the cases so that all
1224 // enum counters deeper than enumdepth is also reset.
1225 switch (pit->enumdepth) {
1227 textclass.counters().reset("enumi");
1229 textclass.counters().reset("enumii");
1231 textclass.counters().reset("enumiii");
1233 textclass.counters().reset("enumiv");
1239 // Updates all counters. Paragraphs with changed label string will be rebroken
1240 void LyXText::updateCounters()
1242 RowList::iterator rowit = rows().begin();
1243 ParagraphList::iterator pit = rowit->par();
1245 // CHECK if this is really needed. (Lgb)
1246 bv()->buffer()->params.getLyXTextClass().counters().reset();
1248 for (; pit != ownerParagraphs().end(); ++pit) {
1249 while (rowit->par() != pit)
1252 string const oldLabel = pit->params().labelString();
1254 size_t maxdepth = 0;
1255 if (pit != ownerParagraphs().begin())
1256 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1258 if (pit->params().depth() > maxdepth)
1259 pit->params().depth(maxdepth);
1261 // setCounter can potentially change the labelString.
1262 setCounter(bv()->buffer(), pit);
1264 string const & newLabel = pit->params().labelString();
1266 if (oldLabel.empty() && !newLabel.empty()) {
1267 removeParagraph(rowit);
1268 appendParagraph(rowit);
1274 void LyXText::insertInset(Inset * inset)
1276 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1278 setUndo(bv(), Undo::FINISH, cursor.par(),
1279 boost::next(cursor.par()));
1281 cursor.par()->insertInset(cursor.pos(), inset);
1282 // Just to rebreak and refresh correctly.
1283 // The character will not be inserted a second time
1284 insertChar(Paragraph::META_INSET);
1285 // If we enter a highly editable inset the cursor should be to before
1286 // the inset. This couldn't happen before as Undo was not handled inside
1287 // inset now after the Undo LyX tries to call inset->Edit(...) again
1288 // and cannot do this as the cursor is behind the inset and GetInset
1289 // does not return the inset!
1290 if (isHighlyEditableInset(inset)) {
1297 void LyXText::cutSelection(bool doclear, bool realcut)
1299 // Stuff what we got on the clipboard. Even if there is no selection.
1301 // There is a problem with having the stuffing here in that the
1302 // larger the selection the slower LyX will get. This can be
1303 // solved by running the line below only when the selection has
1304 // finished. The solution used currently just works, to make it
1305 // faster we need to be more clever and probably also have more
1306 // calls to stuffClipboard. (Lgb)
1307 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1309 // This doesn't make sense, if there is no selection
1310 if (!selection.set())
1313 // OK, we have a selection. This is always between selection.start
1314 // and selection.end
1316 // make sure that the depth behind the selection are restored, too
1317 ParagraphList::iterator endpit = boost::next(selection.end.par());
1318 ParagraphList::iterator undoendpit = endpit;
1319 ParagraphList::iterator pars_end = ownerParagraphs().end();
1321 if (endpit != pars_end && endpit->getDepth()) {
1322 while (endpit != pars_end && endpit->getDepth()) {
1324 undoendpit = endpit;
1326 } else if (endpit != pars_end) {
1327 // because of parindents etc.
1331 setUndo(bv(), Undo::DELETE, selection.start.par(), undoendpit);
1334 endpit = selection.end.par();
1335 int endpos = selection.end.pos();
1337 boost::tie(endpit, endpos) = realcut ?
1338 CutAndPaste::cutSelection(ownerParagraphs(),
1339 selection.start.par(), endpit,
1340 selection.start.pos(), endpos,
1341 bv()->buffer()->params.textclass,
1343 : CutAndPaste::eraseSelection(ownerParagraphs(),
1344 selection.start.par(), endpit,
1345 selection.start.pos(), endpos,
1347 // sometimes necessary
1349 selection.start.par()->stripLeadingSpaces();
1351 redoParagraphs(selection.start, boost::next(endpit));
1352 #warning FIXME latent bug
1353 // endpit will be invalidated on redoParagraphs once ParagraphList
1354 // becomes a std::list? There are maybe other places on which this
1355 // can happend? (Ab)
1356 // cutSelection can invalidate the cursor so we need to set
1358 // we prefer the end for when tracking changes
1362 // need a valid cursor. (Lgb)
1365 setCursor(cursor.par(), cursor.pos());
1366 selection.cursor = cursor;
1371 void LyXText::copySelection()
1373 // stuff the selection onto the X clipboard, from an explicit copy request
1374 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1376 // this doesnt make sense, if there is no selection
1377 if (!selection.set())
1380 // ok we have a selection. This is always between selection.start
1381 // and sel_end cursor
1383 // copy behind a space if there is one
1384 while (selection.start.par()->size() > selection.start.pos()
1385 && selection.start.par()->isLineSeparator(selection.start.pos())
1386 && (selection.start.par() != selection.end.par()
1387 || selection.start.pos() < selection.end.pos()))
1388 selection.start.pos(selection.start.pos() + 1);
1390 CutAndPaste::copySelection(selection.start.par(),
1391 selection.end.par(),
1392 selection.start.pos(), selection.end.pos(),
1393 bv()->buffer()->params.textclass);
1397 void LyXText::pasteSelection()
1399 // this does not make sense, if there is nothing to paste
1400 if (!CutAndPaste::checkPastePossible())
1403 setUndo(bv(), Undo::INSERT,
1404 cursor.par(), boost::next(cursor.par()));
1406 ParagraphList::iterator endpit;
1409 boost::tie(ppp, endpit) =
1410 CutAndPaste::pasteSelection(ownerParagraphs(),
1411 cursor.par(), cursor.pos(),
1412 bv()->buffer()->params.textclass);
1414 redoParagraphs(cursor, endpit);
1416 setCursor(cursor.par(), cursor.pos());
1419 selection.cursor = cursor;
1420 setCursor(ppp.first, ppp.second);
1426 void LyXText::setSelectionRange(lyx::pos_type length)
1431 selection.cursor = cursor;
1438 // simple replacing. The font of the first selected character is used
1439 void LyXText::replaceSelectionWithString(string const & str)
1441 setCursorParUndo(bv());
1444 if (!selection.set()) { // create a dummy selection
1445 selection.end = cursor;
1446 selection.start = cursor;
1449 // Get font setting before we cut
1450 pos_type pos = selection.end.pos();
1451 LyXFont const font = selection.start.par()
1452 ->getFontSettings(bv()->buffer()->params,
1453 selection.start.pos());
1455 // Insert the new string
1456 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1457 selection.end.par()->insertChar(pos, (*cit), font);
1461 // Cut the selection
1462 cutSelection(true, false);
1468 // needed to insert the selection
1469 void LyXText::insertStringAsLines(string const & str)
1471 ParagraphList::iterator pit = cursor.par();
1472 pos_type pos = cursor.pos();
1473 ParagraphList::iterator endpit = boost::next(cursor.par());
1475 setCursorParUndo(bv());
1477 // only to be sure, should not be neccessary
1480 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1482 redoParagraphs(cursor, endpit);
1483 setCursor(cursor.par(), cursor.pos());
1484 selection.cursor = cursor;
1485 setCursor(pit, pos);
1490 // turns double-CR to single CR, others where converted into one
1491 // blank. Then InsertStringAsLines is called
1492 void LyXText::insertStringAsParagraphs(string const & str)
1494 string linestr(str);
1495 bool newline_inserted = false;
1496 for (string::size_type i = 0; i < linestr.length(); ++i) {
1497 if (linestr[i] == '\n') {
1498 if (newline_inserted) {
1499 // we know that \r will be ignored by
1500 // InsertStringA. Of course, it is a dirty
1501 // trick, but it works...
1502 linestr[i - 1] = '\r';
1506 newline_inserted = true;
1508 } else if (IsPrintable(linestr[i])) {
1509 newline_inserted = false;
1512 insertStringAsLines(linestr);
1516 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1518 LyXCursor tmpcursor;
1522 RowList::iterator row = getRow(pit, pos, y);
1523 RowList::iterator beg = rows().begin();
1525 // is there a break one row above
1527 && boost::prior(row)->par() == row->par()) {
1528 z = rowBreakPoint(*boost::prior(row));
1529 if (z >= row->pos()) {
1530 // set the dimensions of the row above
1531 y -= boost::prior(row)->height();
1534 breakAgain(boost::prior(row));
1536 // set the cursor again. Otherwise
1537 // dangling pointers are possible
1538 setCursor(cursor.par(), cursor.pos(),
1539 false, cursor.boundary());
1540 selection.cursor = cursor;
1545 int const tmpheight = row->height();
1546 pos_type const tmplast = lastPos(*this, row);
1549 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1550 postRowPaint(row, y);
1555 // check the special right address boxes
1556 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1563 redoDrawingOfParagraph(tmpcursor);
1566 // set the cursor again. Otherwise dangling pointers are possible
1567 // also set the selection
1569 if (selection.set()) {
1571 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1572 false, selection.cursor.boundary());
1573 selection.cursor = cursor;
1574 setCursorIntern(selection.start.par(),
1575 selection.start.pos(),
1576 false, selection.start.boundary());
1577 selection.start = cursor;
1578 setCursorIntern(selection.end.par(),
1579 selection.end.pos(),
1580 false, selection.end.boundary());
1581 selection.end = cursor;
1582 setCursorIntern(last_sel_cursor.par(),
1583 last_sel_cursor.pos(),
1584 false, last_sel_cursor.boundary());
1585 last_sel_cursor = cursor;
1588 setCursorIntern(cursor.par(), cursor.pos(),
1589 false, cursor.boundary());
1593 // returns false if inset wasn't found
1594 bool LyXText::updateInset(Inset * inset)
1596 // first check the current paragraph
1597 int pos = cursor.par()->getPositionOfInset(inset);
1599 checkParagraph(cursor.par(), pos);
1603 // check every paragraph
1605 ParagraphList::iterator par = ownerParagraphs().begin();
1606 ParagraphList::iterator end = ownerParagraphs().end();
1607 for (; par != end; ++par) {
1608 pos = par->getPositionOfInset(inset);
1610 checkParagraph(par, pos);
1619 bool LyXText::setCursor(ParagraphList::iterator pit,
1621 bool setfont, bool boundary)
1623 LyXCursor old_cursor = cursor;
1624 setCursorIntern(pit, pos, setfont, boundary);
1625 return deleteEmptyParagraphMechanism(old_cursor);
1629 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1630 pos_type pos, bool boundary)
1632 lyx::Assert(pit != ownerParagraphs().end());
1636 cur.boundary(boundary);
1638 // get the cursor y position in text
1640 RowList::iterator row = getRow(pit, pos, y);
1641 RowList::iterator beg = rows().begin();
1643 RowList::iterator old_row = row;
1645 // if we are before the first char of this row and are still in the
1646 // same paragraph and there is a previous row then put the cursor on
1647 // the end of the previous row
1648 cur.iy(y + row->baseline());
1651 boost::prior(row)->par() == row->par() &&
1652 pos < pit->size() &&
1653 pit->getChar(pos) == Paragraph::META_INSET) {
1654 Inset * ins = pit->getInset(pos);
1655 if (ins && (ins->needFullRow() || ins->display())) {
1662 // y is now the beginning of the cursor row
1663 y += row->baseline();
1664 // y is now the cursor baseline
1667 pos_type last = lastPrintablePos(*this, old_row);
1669 // None of these should happen, but we're scaredy-cats
1670 if (pos > pit->size()) {
1671 lyxerr << "dont like 1 please report" << endl;
1674 } else if (pos > last + 1) {
1675 lyxerr << "dont like 2 please report" << endl;
1676 // This shouldn't happen.
1679 } else if (pos < row->pos()) {
1680 lyxerr << "dont like 3 please report" << endl;
1685 // now get the cursors x position
1686 float x = getCursorX(row, pos, last, boundary);
1689 if (old_row != row) {
1690 x = getCursorX(old_row, pos, last, boundary);
1694 /* We take out this for the time being because 1) the redraw code is not
1695 prepared to this yet and 2) because some good policy has yet to be decided
1696 while editting: for instance how to act on rows being created/deleted
1700 //if the cursor is in a visible row, anchor to it
1702 if (topy < y && y < topy + bv()->workHeight())
1708 float LyXText::getCursorX(RowList::iterator rit,
1709 pos_type pos, pos_type last, bool boundary) const
1711 pos_type cursor_vpos = 0;
1713 float fill_separator;
1715 float fill_label_hfill;
1716 // This call HAS to be here because of the BidiTables!!!
1717 prepareToPrint(rit, x, fill_separator, fill_hfill,
1720 ParagraphList::iterator rit_par = rit->par();
1721 pos_type const rit_pos = rit->pos();
1724 cursor_vpos = rit_pos;
1725 else if (pos > last && !boundary)
1726 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1727 ? rit_pos : last + 1;
1728 else if (pos > rit_pos && (pos > last || boundary))
1729 /// Place cursor after char at (logical) position pos - 1
1730 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1731 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1733 /// Place cursor before char at (logical) position pos
1734 cursor_vpos = (bidi_level(pos) % 2 == 0)
1735 ? log2vis(pos) : log2vis(pos) + 1;
1737 pos_type body_pos = rit_par->beginningOfBody();
1738 if ((body_pos > 0) &&
1739 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1742 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1743 pos_type pos = vis2log(vpos);
1744 if (body_pos > 0 && pos == body_pos - 1) {
1745 x += fill_label_hfill +
1746 font_metrics::width(
1747 rit_par->layout()->labelsep,
1748 getLabelFont(bv()->buffer(), rit_par));
1749 if (rit_par->isLineSeparator(body_pos - 1))
1750 x -= singleWidth(rit_par, body_pos - 1);
1753 if (hfillExpansion(*this, rit, pos)) {
1754 x += singleWidth(rit_par, pos);
1755 if (pos >= body_pos)
1758 x += fill_label_hfill;
1759 } else if (rit_par->isSeparator(pos)) {
1760 x += singleWidth(rit_par, pos);
1761 if (pos >= body_pos)
1762 x += fill_separator;
1764 x += singleWidth(rit_par, pos);
1770 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1771 pos_type pos, bool setfont, bool boundary)
1773 InsetText * it = static_cast<InsetText *>(pit->inInset());
1775 if (it != inset_owner) {
1776 lyxerr[Debug::INSETS] << "InsetText is " << it
1778 << "inset_owner is "
1779 << inset_owner << endl;
1780 #ifdef WITH_WARNINGS
1781 #warning I believe this code is wrong. (Lgb)
1782 #warning Jürgen, have a look at this. (Lgb)
1783 #warning Hmmm, I guess you are right but we
1784 #warning should verify when this is needed
1786 // Jürgen, would you like to have a look?
1787 // I guess we need to move the outer cursor
1788 // and open and lock the inset (bla bla bla)
1789 // stuff I don't know... so can you have a look?
1791 // I moved the lyxerr stuff in here so we can see if
1792 // this is actually really needed and where!
1794 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1799 setCursor(cursor, pit, pos, boundary);
1805 void LyXText::setCurrentFont()
1807 pos_type pos = cursor.pos();
1808 ParagraphList::iterator pit = cursor.par();
1810 if (cursor.boundary() && pos > 0)
1814 if (pos == pit->size())
1816 else // potentional bug... BUG (Lgb)
1817 if (pit->isSeparator(pos)) {
1818 if (pos > cursor.row()->pos() &&
1819 bidi_level(pos) % 2 ==
1820 bidi_level(pos - 1) % 2)
1822 else if (pos + 1 < pit->size())
1828 pit->getFontSettings(bv()->buffer()->params, pos);
1829 real_current_font = getFont(bv()->buffer(), pit, pos);
1831 if (cursor.pos() == pit->size() &&
1832 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1833 !cursor.boundary()) {
1834 Language const * lang =
1835 pit->getParLanguage(bv()->buffer()->params);
1836 current_font.setLanguage(lang);
1837 current_font.setNumber(LyXFont::OFF);
1838 real_current_font.setLanguage(lang);
1839 real_current_font.setNumber(LyXFont::OFF);
1844 // returns the column near the specified x-coordinate of the row
1845 // x is set to the real beginning of this column
1847 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1850 float fill_separator;
1852 float fill_label_hfill;
1854 prepareToPrint(rit, tmpx, fill_separator,
1855 fill_hfill, fill_label_hfill);
1857 pos_type vc = rit->pos();
1858 pos_type last = lastPrintablePos(*this, rit);
1861 ParagraphList::iterator rit_par = rit->par();
1862 LyXLayout_ptr const & layout = rit->par()->layout();
1864 bool left_side = false;
1866 pos_type body_pos = rit_par->beginningOfBody();
1867 float last_tmpx = tmpx;
1870 (body_pos - 1 > last ||
1871 !rit_par->isLineSeparator(body_pos - 1)))
1874 // check for empty row
1875 if (!rit_par->size()) {
1880 while (vc <= last && tmpx <= x) {
1883 if (body_pos > 0 && c == body_pos - 1) {
1884 tmpx += fill_label_hfill +
1885 font_metrics::width(layout->labelsep,
1886 getLabelFont(bv()->buffer(), rit_par));
1887 if (rit_par->isLineSeparator(body_pos - 1))
1888 tmpx -= singleWidth(rit_par, body_pos - 1);
1891 if (hfillExpansion(*this, rit, c)) {
1892 tmpx += singleWidth(rit_par, c);
1896 tmpx += fill_label_hfill;
1897 } else if (rit_par->isSeparator(c)) {
1898 tmpx += singleWidth(rit_par, c);
1900 tmpx+= fill_separator;
1902 tmpx += singleWidth(rit_par, c);
1907 if ((tmpx + last_tmpx) / 2 > x) {
1912 if (vc > last + 1) // This shouldn't happen.
1916 // This (rtl_support test) is not needed, but gives
1917 // some speedup if rtl_support=false
1918 bool const lastrow = lyxrc.rtl_support &&
1919 (boost::next(rit) == rowlist_.end() ||
1920 boost::next(rit)->par() != rit_par);
1921 // If lastrow is false, we don't need to compute
1922 // the value of rtl.
1923 bool const rtl = (lastrow)
1924 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1927 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1928 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1930 else if (vc == rit->pos()) {
1932 if (bidi_level(c) % 2 == 1)
1935 c = vis2log(vc - 1);
1936 bool const rtl = (bidi_level(c) % 2 == 1);
1937 if (left_side == rtl) {
1939 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1943 if (rit->pos() <= last && c > last
1944 && rit_par->isNewline(last)) {
1945 if (bidi_level(last) % 2 == 0)
1946 tmpx -= singleWidth(rit_par, last);
1948 tmpx += singleWidth(rit_par, last);
1958 void LyXText::setCursorFromCoordinates(int x, int y)
1960 LyXCursor old_cursor = cursor;
1962 setCursorFromCoordinates(cursor, x, y);
1964 deleteEmptyParagraphMechanism(old_cursor);
1971 * return true if the cursor given is at the end of a row,
1972 * and the next row is filled by an inset that spans an entire
1975 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur) {
1976 RowList::iterator row = cur.row();
1977 if (boost::next(row) == lt.rows().end())
1979 Row const & next = *boost::next(row);
1981 if (next.pos() != cur.pos() || next.par() != cur.par())
1984 if (cur.pos() == cur.par()->size()
1985 || !cur.par()->isInset(cur.pos()))
1987 Inset const * inset = cur.par()->getInset(cur.pos());
1988 if (inset->needFullRow() || inset->display())
1995 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1997 // Get the row first.
1999 RowList::iterator row = getRowNearY(y);
2001 pos_type const column = getColumnNearX(row, x, bound);
2002 cur.par(row->par());
2003 cur.pos(row->pos() + column);
2005 cur.y(y + row->baseline());
2008 if (beforeFullRowInset(*this, cur)) {
2009 pos_type last = lastPrintablePos(*this, row);
2010 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2012 cur.iy(y + row->height() + boost::next(row)->baseline());
2013 cur.irow(boost::next(row));
2019 cur.boundary(bound);
2023 void LyXText::cursorLeft(bool internal)
2025 if (cursor.pos() > 0) {
2026 bool boundary = cursor.boundary();
2027 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2028 if (!internal && !boundary &&
2029 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2030 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2031 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2032 ParagraphList::iterator pit = boost::prior(cursor.par());
2033 setCursor(pit, pit->size());
2038 void LyXText::cursorRight(bool internal)
2040 bool const at_end = (cursor.pos() == cursor.par()->size());
2041 bool const at_newline = !at_end &&
2042 cursor.par()->isNewline(cursor.pos());
2044 if (!internal && cursor.boundary() && !at_newline)
2045 setCursor(cursor.par(), cursor.pos(), true, false);
2047 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2049 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2050 setCursor(cursor.par(), cursor.pos(), true, true);
2051 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2052 setCursor(boost::next(cursor.par()), 0);
2056 void LyXText::cursorUp(bool selecting)
2059 int x = cursor.x_fix();
2060 int y = cursor.y() - cursor.row()->baseline() - 1;
2061 setCursorFromCoordinates(x, y);
2064 int y1 = cursor.iy() - topy;
2067 Inset * inset_hit = checkInsetHit(x, y1);
2068 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2069 inset_hit->localDispatch(
2070 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
2074 setCursorFromCoordinates(bv(), cursor.x_fix(),
2075 cursor.y() - cursor.row()->baseline() - 1);
2080 void LyXText::cursorDown(bool selecting)
2083 int x = cursor.x_fix();
2084 int y = cursor.y() - cursor.row()->baseline() +
2085 cursor.row()->height() + 1;
2086 setCursorFromCoordinates(x, y);
2087 if (!selecting && cursor.row() == cursor.irow()) {
2089 int y1 = cursor.iy() - topy;
2092 Inset * inset_hit = checkInsetHit(x, y1);
2093 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2094 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
2095 inset_hit->localDispatch(cmd);
2099 setCursorFromCoordinates(bv(), cursor.x_fix(),
2100 cursor.y() - cursor.row()->baseline()
2101 + cursor.row()->height() + 1);
2106 void LyXText::cursorUpParagraph()
2108 if (cursor.pos() > 0) {
2109 setCursor(cursor.par(), 0);
2111 else if (cursor.par() != ownerParagraphs().begin()) {
2112 setCursor(boost::prior(cursor.par()), 0);
2117 void LyXText::cursorDownParagraph()
2119 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2120 setCursor(boost::next(cursor.par()), 0);
2122 setCursor(cursor.par(), cursor.par()->size());
2126 // fix the cursor `cur' after a characters has been deleted at `where'
2127 // position. Called by deleteEmptyParagraphMechanism
2128 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2129 LyXCursor const & where)
2131 // if cursor is not in the paragraph where the delete occured,
2133 if (cur.par() != where.par())
2136 // if cursor position is after the place where the delete occured,
2138 if (cur.pos() > where.pos())
2139 cur.pos(cur.pos()-1);
2141 // check also if we don't want to set the cursor on a spot behind the
2142 // pagragraph because we erased the last character.
2143 if (cur.pos() > cur.par()->size())
2144 cur.pos(cur.par()->size());
2146 // recompute row et al. for this cursor
2147 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2151 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2153 // Would be wrong to delete anything if we have a selection.
2154 if (selection.set())
2157 // We allow all kinds of "mumbo-jumbo" when freespacing.
2158 if (old_cursor.par()->layout()->free_spacing
2159 || old_cursor.par()->isFreeSpacing()) {
2163 /* Ok I'll put some comments here about what is missing.
2164 I have fixed BackSpace (and thus Delete) to not delete
2165 double-spaces automagically. I have also changed Cut,
2166 Copy and Paste to hopefully do some sensible things.
2167 There are still some small problems that can lead to
2168 double spaces stored in the document file or space at
2169 the beginning of paragraphs. This happens if you have
2170 the cursor betwenn to spaces and then save. Or if you
2171 cut and paste and the selection have a space at the
2172 beginning and then save right after the paste. I am
2173 sure none of these are very hard to fix, but I will
2174 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2175 that I can get some feedback. (Lgb)
2178 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2179 // delete the LineSeparator.
2182 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2183 // delete the LineSeparator.
2186 // If the pos around the old_cursor were spaces, delete one of them.
2187 if (old_cursor.par() != cursor.par()
2188 || old_cursor.pos() != cursor.pos()) {
2189 // Only if the cursor has really moved
2191 if (old_cursor.pos() > 0
2192 && old_cursor.pos() < old_cursor.par()->size()
2193 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2194 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2195 old_cursor.par()->erase(old_cursor.pos() - 1);
2196 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2198 #ifdef WITH_WARNINGS
2199 #warning This will not work anymore when we have multiple views of the same buffer
2200 // In this case, we will have to correct also the cursors held by
2201 // other bufferviews. It will probably be easier to do that in a more
2202 // automated way in LyXCursor code. (JMarc 26/09/2001)
2204 // correct all cursors held by the LyXText
2205 fixCursorAfterDelete(cursor, old_cursor);
2206 fixCursorAfterDelete(selection.cursor,
2208 fixCursorAfterDelete(selection.start,
2210 fixCursorAfterDelete(selection.end, old_cursor);
2211 fixCursorAfterDelete(last_sel_cursor,
2213 fixCursorAfterDelete(toggle_cursor, old_cursor);
2214 fixCursorAfterDelete(toggle_end_cursor,
2220 // don't delete anything if this is the ONLY paragraph!
2221 if (ownerParagraphs().size() == 1)
2224 // Do not delete empty paragraphs with keepempty set.
2225 if (old_cursor.par()->layout()->keepempty)
2228 // only do our magic if we changed paragraph
2229 if (old_cursor.par() == cursor.par())
2232 // record if we have deleted a paragraph
2233 // we can't possibly have deleted a paragraph before this point
2234 bool deleted = false;
2236 if (old_cursor.par()->empty() ||
2237 (old_cursor.par()->size() == 1 &&
2238 old_cursor.par()->isLineSeparator(0))) {
2239 // ok, we will delete anything
2240 LyXCursor tmpcursor;
2244 bool selection_position_was_oldcursor_position = (
2245 selection.cursor.par() == old_cursor.par()
2246 && selection.cursor.pos() == old_cursor.pos());
2248 if (old_cursor.row() != rows().begin()) {
2250 prevrow = boost::prior(old_cursor.row());
2251 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2253 cursor = old_cursor; // that undo can restore the right cursor position
2254 #warning FIXME. --end() iterator is usable here
2255 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2256 while (endpit != ownerParagraphs().end() &&
2257 endpit->getDepth()) {
2261 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpit);
2265 removeRow(old_cursor.row());
2267 ownerParagraphs().erase(old_cursor.par());
2269 /* Breakagain the next par. Needed because of
2270 * the parindent that can occur or dissappear.
2271 * The next row can change its height, if
2272 * there is another layout before */
2273 if (boost::next(prevrow) != rows().end()) {
2274 breakAgain(boost::next(prevrow));
2277 setHeightOfRow(prevrow);
2279 RowList::iterator nextrow = boost::next(old_cursor.row());
2280 const_cast<LyXText *>(this)->postPaint(
2281 old_cursor.y() - old_cursor.row()->baseline());
2284 cursor = old_cursor; // that undo can restore the right cursor position
2285 #warning FIXME. --end() iterator is usable here
2286 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2287 while (endpit != ownerParagraphs().end() &&
2288 endpit->getDepth()) {
2292 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpit);
2296 removeRow(old_cursor.row());
2298 ownerParagraphs().erase(old_cursor.par());
2300 /* Breakagain the next par. Needed because of
2301 the parindent that can occur or dissappear.
2302 The next row can change its height, if
2303 there is another layout before */
2304 if (nextrow != rows().end()) {
2305 breakAgain(nextrow);
2311 setCursorIntern(cursor.par(), cursor.pos());
2313 if (selection_position_was_oldcursor_position) {
2314 // correct selection
2315 selection.cursor = cursor;
2319 if (old_cursor.par()->stripLeadingSpaces()) {
2320 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2322 setCursorIntern(cursor.par(), cursor.pos());
2323 selection.cursor = cursor;
2330 ParagraphList & LyXText::ownerParagraphs() const
2333 return inset_owner->paragraphs;
2335 return bv_owner->buffer()->paragraphs;
2339 LyXText::refresh_status LyXText::refreshStatus() const
2341 return refresh_status_;
2345 void LyXText::clearPaint()
2347 refresh_status_ = REFRESH_NONE;
2348 refresh_row = rows().end();
2353 void LyXText::postPaint(int start_y)
2355 refresh_status old = refresh_status_;
2357 refresh_status_ = REFRESH_AREA;
2358 refresh_row = rows().end();
2360 if (old != REFRESH_NONE && refresh_y < start_y)
2363 refresh_y = start_y;
2368 // We are an inset's lyxtext. Tell the top-level lyxtext
2369 // it needs to update the row we're in.
2370 LyXText * t = bv()->text;
2371 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2375 // FIXME: we should probably remove this y parameter,
2376 // make refresh_y be 0, and use row->y etc.
2377 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2379 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2380 refresh_status_ = REFRESH_AREA;
2383 refresh_y = start_y;
2386 if (refresh_status_ == REFRESH_AREA)
2389 refresh_status_ = REFRESH_ROW;
2395 // We are an inset's lyxtext. Tell the top-level lyxtext
2396 // it needs to update the row we're in.
2397 LyXText * t = bv()->text;
2398 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2402 bool LyXText::isInInset() const
2404 // Sub-level has non-null bv owner and
2405 // non-null inset owner.
2406 return inset_owner != 0 && bv_owner != 0;
2410 int defaultRowHeight()
2412 LyXFont const font(LyXFont::ALL_SANE);
2413 return int(font_metrics::maxAscent(font)
2414 + font_metrics::maxDescent(font) * 1.5);