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(static_cast<UpdatableInset *>(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(), 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, pastend);
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,
592 selection.start.par(), boost::next(selection.end.par()));
594 cursor = selection.start;
595 while (cursor.par() != selection.end.par() ||
596 cursor.pos() < selection.end.pos())
598 if (cursor.pos() < cursor.par()->size()) {
599 // an open footnote should behave like a closed one
600 setCharFont(cursor.par(), cursor.pos(),
602 cursor.pos(cursor.pos() + 1);
605 cursor.par(boost::next(cursor.par()));
610 redoParagraphs(selection.start, boost::next(selection.end.par()));
612 // we have to reset the selection, because the
613 // geometry could have changed, but we keep
614 // it for user convenience
615 setCursor(selection.start.par(), selection.start.pos());
616 selection.cursor = cursor;
617 setCursor(selection.end.par(), selection.end.pos());
619 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
620 tmpcursor.boundary());
624 void LyXText::redoHeightOfParagraph()
626 RowList::iterator tmprow = cursor.row();
627 int y = cursor.y() - tmprow->baseline();
629 setHeightOfRow(tmprow);
631 while (tmprow != rows().begin()
632 && boost::prior(tmprow)->par() == tmprow->par()) {
634 y -= tmprow->height();
635 setHeightOfRow(tmprow);
640 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
644 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
646 RowList::iterator tmprow = cur.row();
648 int y = cur.y() - tmprow->baseline();
649 setHeightOfRow(tmprow);
651 while (tmprow != rows().begin()
652 && boost::prior(tmprow)->par() == tmprow->par()) {
654 y -= tmprow->height();
658 setCursor(cur.par(), cur.pos());
662 // deletes and inserts again all paragaphs between the cursor
663 // and the specified par
664 // This function is needed after SetLayout and SetFont etc.
665 void LyXText::redoParagraphs(LyXCursor const & cur,
666 ParagraphList::iterator endpit)
668 RowList::iterator tmprit = cur.row();
669 int y = cur.y() - tmprit->baseline();
671 ParagraphList::iterator first_phys_pit;
672 RowList::iterator prevrit;
673 if (tmprit == rows().begin()) {
674 // A trick/hack for UNDO.
675 // This is needed because in an UNDO/REDO we could have
676 // changed the ownerParagrah() so the paragraph inside
677 // the row is NOT my really first par anymore.
678 // Got it Lars ;) (Jug 20011206)
679 first_phys_pit = ownerParagraphs().begin();
680 prevrit = rows().end();
682 first_phys_pit = tmprit->par();
683 while (tmprit != rows().begin()
684 && boost::prior(tmprit)->par() == first_phys_pit)
687 y -= tmprit->height();
689 prevrit = boost::prior(tmprit);
693 while (tmprit != rows().end() && tmprit->par() != endpit) {
694 RowList::iterator tmprit2 = tmprit++;
698 // Reinsert the paragraphs.
699 ParagraphList::iterator tmppit = first_phys_pit;
701 while (tmppit != ownerParagraphs().end()) {
702 insertParagraph(tmppit, tmprit);
703 while (tmprit != rows().end()
704 && tmprit->par() == tmppit) {
708 if (tmppit == endpit)
711 if (prevrit != rows().end()) {
712 setHeightOfRow(prevrit);
713 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
715 setHeightOfRow(rows().begin());
716 const_cast<LyXText *>(this)->postPaint(0);
718 if (tmprit != rows().end())
719 setHeightOfRow(tmprit);
725 void LyXText::fullRebreak()
727 if (rows().empty()) {
732 RowList::iterator rows_end = rows().end();
734 if (need_break_row != rows_end) {
735 breakAgain(need_break_row);
736 need_break_row = rows_end;
742 // important for the screen
745 // the cursor set functions have a special mechanism. When they
746 // realize, that you left an empty paragraph, they will delete it.
747 // They also delete the corresponding row
749 // need the selection cursor:
750 void LyXText::setSelection()
752 bool const lsel = selection.set();
754 if (!selection.set()) {
755 last_sel_cursor = selection.cursor;
756 selection.start = selection.cursor;
757 selection.end = selection.cursor;
762 // first the toggling area
763 if (cursor.y() < last_sel_cursor.y()
764 || (cursor.y() == last_sel_cursor.y()
765 && cursor.x() < last_sel_cursor.x())) {
766 toggle_end_cursor = last_sel_cursor;
767 toggle_cursor = cursor;
769 toggle_end_cursor = cursor;
770 toggle_cursor = last_sel_cursor;
773 last_sel_cursor = cursor;
775 // and now the whole selection
777 if (selection.cursor.par() == cursor.par())
778 if (selection.cursor.pos() < cursor.pos()) {
779 selection.end = cursor;
780 selection.start = selection.cursor;
782 selection.end = selection.cursor;
783 selection.start = cursor;
785 else if (selection.cursor.y() < cursor.y() ||
786 (selection.cursor.y() == cursor.y()
787 && selection.cursor.x() < cursor.x())) {
788 selection.end = cursor;
789 selection.start = selection.cursor;
792 selection.end = selection.cursor;
793 selection.start = cursor;
796 // a selection with no contents is not a selection
797 if (selection.start.par() == selection.end.par() &&
798 selection.start.pos() == selection.end.pos())
799 selection.set(false);
801 if (inset_owner && (selection.set() || lsel))
802 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
806 string const LyXText::selectionAsString(Buffer const * buffer,
809 if (!selection.set()) return string();
811 // should be const ...
812 ParagraphList::iterator startpit = selection.start.par();
813 ParagraphList::iterator endpit = selection.end.par();
814 pos_type const startpos(selection.start.pos());
815 pos_type const endpos(selection.end.pos());
817 if (startpit == endpit) {
818 return startpit->asString(buffer, startpos, endpos, label);
823 // First paragraph in selection
824 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
826 // The paragraphs in between (if any)
827 ParagraphList::iterator pit = boost::next(startpit);
828 for (; pit != endpit; ++pit) {
829 result += pit->asString(buffer, 0, pit->size(), label) + "\n\n";
832 // Last paragraph in selection
833 result += endpit->asString(buffer, 0, endpos, label);
839 void LyXText::clearSelection()
841 selection.set(false);
842 selection.mark(false);
843 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
844 // reset this in the bv_owner!
845 if (bv_owner && bv_owner->text)
846 bv_owner->text->xsel_cache.set(false);
850 void LyXText::cursorHome()
852 setCursor(cursor.par(), cursor.row()->pos());
856 void LyXText::cursorEnd()
858 if (cursor.par()->empty())
861 RowList::iterator rit = cursor.row();
862 RowList::iterator next_rit = boost::next(rit);
863 ParagraphList::iterator pit = rit->par();
864 pos_type last_pos = lastPos(*this, rit);
866 if (next_rit == rows().end() || next_rit->par() != pit) {
870 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
875 setCursor(pit, last_pos);
879 void LyXText::cursorTop()
881 setCursor(ownerParagraphs().begin(), 0);
885 void LyXText::cursorBottom()
888 // This is how it should be:
889 // ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
890 ParagraphList::iterator lastpit = &ownerParagraphs().back();
891 int pos = lastpit->size();
892 setCursor(lastpit, pos);
896 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
898 // If the mask is completely neutral, tell user
899 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
900 // Could only happen with user style
901 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
905 // Try implicit word selection
906 // If there is a change in the language the implicit word selection
908 LyXCursor resetCursor = cursor;
909 bool implicitSelection = (font.language() == ignore_language
910 && font.number() == LyXFont::IGNORE)
911 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
914 setFont(font, toggleall);
916 // Implicit selections are cleared afterwards
917 //and cursor is set to the original position.
918 if (implicitSelection) {
920 cursor = resetCursor;
921 setCursor(cursor.par(), cursor.pos());
922 selection.cursor = cursor;
925 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
929 string LyXText::getStringToIndex()
931 // Try implicit word selection
932 // If there is a change in the language the implicit word selection
934 LyXCursor const reset_cursor = cursor;
935 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
938 if (!selection.set())
939 bv()->owner()->message(_("Nothing to index!"));
940 else if (selection.start.par() != selection.end.par())
941 bv()->owner()->message(_("Cannot index more than one paragraph!"));
943 idxstring = selectionAsString(bv()->buffer(), false);
945 // Reset cursors to their original position.
946 cursor = reset_cursor;
947 setCursor(cursor.par(), cursor.pos());
948 selection.cursor = cursor;
950 // Clear the implicit selection.
951 if (implicitSelection)
958 // the DTP switches for paragraphs. LyX will store them in the first
959 // physicla paragraph. When a paragraph is broken, the top settings rest,
960 // the bottom settings are given to the new one. So I can make shure,
961 // they do not duplicate themself and you cannnot make dirty things with
964 void LyXText::setParagraph(bool line_top, bool line_bottom,
965 bool pagebreak_top, bool pagebreak_bottom,
966 VSpace const & space_top,
967 VSpace const & space_bottom,
968 Spacing const & spacing,
970 string const & labelwidthstring,
973 LyXCursor tmpcursor = cursor;
974 if (!selection.set()) {
975 selection.start = cursor;
976 selection.end = cursor;
979 // make sure that the depth behind the selection are restored, too
980 ParagraphList::iterator endpit = boost::next(selection.end.par());
981 ParagraphList::iterator undoendpit = endpit;
982 ParagraphList::iterator pars_end = ownerParagraphs().end();
984 if (endpit != pars_end && endpit->getDepth()) {
985 while (endpit != pars_end && endpit->getDepth()) {
989 } else if (endpit != pars_end) {
990 // because of parindents etc.
994 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpit);
997 ParagraphList::iterator tmppit = selection.end.par();
999 while (tmppit != boost::prior(selection.start.par())) {
1000 setCursor(tmppit, 0);
1001 postPaint(cursor.y() - cursor.row()->baseline());
1003 ParagraphList::iterator pit = cursor.par();
1004 ParagraphParameters & params = pit->params();
1006 params.lineTop(line_top);
1007 params.lineBottom(line_bottom);
1008 params.pagebreakTop(pagebreak_top);
1009 params.pagebreakBottom(pagebreak_bottom);
1010 params.spaceTop(space_top);
1011 params.spaceBottom(space_bottom);
1012 params.spacing(spacing);
1013 // does the layout allow the new alignment?
1014 LyXLayout_ptr const & layout = pit->layout();
1016 if (align == LYX_ALIGN_LAYOUT)
1017 align = layout->align;
1018 if (align & layout->alignpossible) {
1019 if (align == layout->align)
1020 params.align(LYX_ALIGN_LAYOUT);
1022 params.align(align);
1024 pit->setLabelWidthString(labelwidthstring);
1025 params.noindent(noindent);
1026 tmppit = boost::prior(pit);
1029 redoParagraphs(selection.start, endpit);
1032 setCursor(selection.start.par(), selection.start.pos());
1033 selection.cursor = cursor;
1034 setCursor(selection.end.par(), selection.end.pos());
1036 setCursor(tmpcursor.par(), tmpcursor.pos());
1038 bv()->updateInset(inset_owner);
1042 // set the counter of a paragraph. This includes the labels
1043 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1045 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1046 LyXLayout_ptr const & layout = pit->layout();
1048 if (pit != ownerParagraphs().begin()) {
1050 pit->params().appendix(boost::prior(pit)->params().appendix());
1051 if (!pit->params().appendix() &&
1052 pit->params().startOfAppendix()) {
1053 pit->params().appendix(true);
1054 textclass.counters().reset();
1056 pit->enumdepth = boost::prior(pit)->enumdepth;
1057 pit->itemdepth = boost::prior(pit)->itemdepth;
1059 pit->params().appendix(pit->params().startOfAppendix());
1064 /* Maybe we have to increment the enumeration depth.
1065 * BUT, enumeration in a footnote is considered in isolation from its
1066 * surrounding paragraph so don't increment if this is the
1067 * first line of the footnote
1068 * AND, bibliographies can't have their depth changed ie. they
1069 * are always of depth 0
1071 if (pit != ownerParagraphs().begin()
1072 && boost::prior(pit)->getDepth() < pit->getDepth()
1073 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1074 && pit->enumdepth < 3
1075 && layout->labeltype != LABEL_BIBLIO) {
1079 // Maybe we have to decrement the enumeration depth, see note above
1080 if (pit != ownerParagraphs().begin()
1081 && boost::prior(pit)->getDepth() > pit->getDepth()
1082 && layout->labeltype != LABEL_BIBLIO) {
1083 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1084 pit->getDepth())->enumdepth;
1087 if (!pit->params().labelString().empty()) {
1088 pit->params().labelString(string());
1091 if (layout->margintype == MARGIN_MANUAL) {
1092 if (pit->params().labelWidthString().empty()) {
1093 pit->setLabelWidthString(layout->labelstring());
1096 pit->setLabelWidthString(string());
1099 // is it a layout that has an automatic label?
1100 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1101 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1105 if (i >= 0 && i <= buf->params.secnumdepth) {
1109 textclass.counters().step(layout->latexname());
1111 // Is there a label? Useful for Chapter layout
1112 if (!pit->params().appendix()) {
1113 s << buf->B_(layout->labelstring());
1115 s << buf->B_(layout->labelstring_appendix());
1118 // Use of an integer is here less than elegant. For now.
1119 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1120 if (!pit->params().appendix()) {
1121 numbertype = "sectioning";
1123 numbertype = "appendix";
1124 if (pit->isRightToLeftPar(buf->params))
1125 langtype = "hebrew";
1131 << textclass.counters()
1132 .numberLabel(layout->latexname(),
1133 numbertype, langtype, head);
1135 pit->params().labelString(STRCONV(s.str()));
1137 // reset enum counters
1138 textclass.counters().reset("enum");
1139 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1140 textclass.counters().reset("enum");
1141 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1143 // Yes I know this is a really, really! bad solution
1145 string enumcounter("enum");
1147 switch (pit->enumdepth) {
1156 enumcounter += "iv";
1159 // not a valid enumdepth...
1163 textclass.counters().step(enumcounter);
1165 s << textclass.counters()
1166 .numberLabel(enumcounter, "enumeration");
1167 pit->params().labelString(STRCONV(s.str()));
1169 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1170 textclass.counters().step("bibitem");
1171 int number = textclass.counters().value("bibitem");
1172 if (pit->bibitem()) {
1173 pit->bibitem()->setCounter(number);
1174 pit->params().labelString(layout->labelstring());
1176 // In biblio should't be following counters but...
1178 string s = buf->B_(layout->labelstring());
1180 // the caption hack:
1181 if (layout->labeltype == LABEL_SENSITIVE) {
1182 ParagraphList::iterator tmppit = pit;
1185 while (tmppit != ownerParagraphs().end() &&
1187 // the single '=' is intended below
1188 && (in = tmppit->inInset()->owner())) {
1189 if (in->lyxCode() == Inset::FLOAT_CODE ||
1190 in->lyxCode() == Inset::WRAP_CODE) {
1194 tmppit = in->parOwner();
1201 if (in->lyxCode() == Inset::FLOAT_CODE)
1202 type = static_cast<InsetFloat*>(in)->params().type;
1203 else if (in->lyxCode() == Inset::WRAP_CODE)
1204 type = static_cast<InsetWrap*>(in)->params().type;
1208 Floating const & fl = textclass.floats().getType(type);
1210 textclass.counters().step(fl.type());
1212 // Doesn't work... yet.
1213 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1215 // par->SetLayout(0);
1216 // s = layout->labelstring;
1217 s = _("Senseless: ");
1220 pit->params().labelString(s);
1222 // reset the enumeration counter. They are always reset
1223 // when there is any other layout between
1224 // Just fall-through between the cases so that all
1225 // enum counters deeper than enumdepth is also reset.
1226 switch (pit->enumdepth) {
1228 textclass.counters().reset("enumi");
1230 textclass.counters().reset("enumii");
1232 textclass.counters().reset("enumiii");
1234 textclass.counters().reset("enumiv");
1240 // Updates all counters. Paragraphs with changed label string will be rebroken
1241 void LyXText::updateCounters()
1243 RowList::iterator rowit = rows().begin();
1244 ParagraphList::iterator pit = rowit->par();
1246 // CHECK if this is really needed. (Lgb)
1247 bv()->buffer()->params.getLyXTextClass().counters().reset();
1249 for (; pit != ownerParagraphs().end(); ++pit) {
1250 while (rowit->par() != pit)
1253 string const oldLabel = pit->params().labelString();
1255 size_t maxdepth = 0;
1256 if (pit != ownerParagraphs().begin())
1257 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1259 if (pit->params().depth() > maxdepth)
1260 pit->params().depth(maxdepth);
1262 // setCounter can potentially change the labelString.
1263 setCounter(bv()->buffer(), pit);
1265 string const & newLabel = pit->params().labelString();
1267 if (oldLabel.empty() && !newLabel.empty()) {
1268 removeParagraph(rowit);
1269 appendParagraph(rowit);
1275 void LyXText::insertInset(Inset * inset)
1277 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1279 setUndo(bv(), Undo::FINISH, cursor.par(),
1280 boost::next(cursor.par()));
1282 cursor.par()->insertInset(cursor.pos(), inset);
1283 // Just to rebreak and refresh correctly.
1284 // The character will not be inserted a second time
1285 insertChar(Paragraph::META_INSET);
1286 // If we enter a highly editable inset the cursor should be to before
1287 // the inset. This couldn't happen before as Undo was not handled inside
1288 // inset now after the Undo LyX tries to call inset->Edit(...) again
1289 // and cannot do this as the cursor is behind the inset and GetInset
1290 // does not return the inset!
1291 if (isHighlyEditableInset(inset)) {
1298 void LyXText::cutSelection(bool doclear, bool realcut)
1300 // Stuff what we got on the clipboard. Even if there is no selection.
1302 // There is a problem with having the stuffing here in that the
1303 // larger the selection the slower LyX will get. This can be
1304 // solved by running the line below only when the selection has
1305 // finished. The solution used currently just works, to make it
1306 // faster we need to be more clever and probably also have more
1307 // calls to stuffClipboard. (Lgb)
1308 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1310 // This doesn't make sense, if there is no selection
1311 if (!selection.set())
1314 // OK, we have a selection. This is always between selection.start
1315 // and selection.end
1317 // make sure that the depth behind the selection are restored, too
1318 ParagraphList::iterator endpit = boost::next(selection.end.par());
1319 ParagraphList::iterator undoendpit = endpit;
1320 ParagraphList::iterator pars_end = ownerParagraphs().end();
1322 if (endpit != pars_end && endpit->getDepth()) {
1323 while (endpit != pars_end && endpit->getDepth()) {
1325 undoendpit = endpit;
1327 } else if (endpit != pars_end) {
1328 // because of parindents etc.
1332 setUndo(bv(), Undo::DELETE, selection.start.par(), undoendpit);
1335 endpit = selection.end.par();
1336 int endpos = selection.end.pos();
1338 boost::tie(endpit, endpos) = realcut ?
1339 CutAndPaste::cutSelection(ownerParagraphs(),
1340 selection.start.par(), endpit,
1341 selection.start.pos(), endpos,
1342 bv()->buffer()->params.textclass,
1344 : CutAndPaste::eraseSelection(ownerParagraphs(),
1345 selection.start.par(), endpit,
1346 selection.start.pos(), endpos,
1348 // sometimes necessary
1350 selection.start.par()->stripLeadingSpaces();
1352 redoParagraphs(selection.start, boost::next(endpit));
1353 #warning FIXME latent bug
1354 // endpit will be invalidated on redoParagraphs once ParagraphList
1355 // becomes a std::list? There are maybe other places on which this
1356 // can happend? (Ab)
1357 // cutSelection can invalidate the cursor so we need to set
1359 // we prefer the end for when tracking changes
1363 // need a valid cursor. (Lgb)
1366 setCursor(cursor.par(), cursor.pos());
1367 selection.cursor = cursor;
1372 void LyXText::copySelection()
1374 // stuff the selection onto the X clipboard, from an explicit copy request
1375 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1377 // this doesnt make sense, if there is no selection
1378 if (!selection.set())
1381 // ok we have a selection. This is always between selection.start
1382 // and sel_end cursor
1384 // copy behind a space if there is one
1385 while (selection.start.par()->size() > selection.start.pos()
1386 && selection.start.par()->isLineSeparator(selection.start.pos())
1387 && (selection.start.par() != selection.end.par()
1388 || selection.start.pos() < selection.end.pos()))
1389 selection.start.pos(selection.start.pos() + 1);
1391 CutAndPaste::copySelection(selection.start.par(),
1392 selection.end.par(),
1393 selection.start.pos(), selection.end.pos(),
1394 bv()->buffer()->params.textclass);
1398 void LyXText::pasteSelection()
1400 // this does not make sense, if there is nothing to paste
1401 if (!CutAndPaste::checkPastePossible())
1404 setUndo(bv(), Undo::INSERT,
1405 cursor.par(), boost::next(cursor.par()));
1407 ParagraphList::iterator endpit;
1412 boost::tie(ppp, endpit) =
1413 CutAndPaste::pasteSelection(ownerParagraphs(),
1414 cursor.par(), cursor.pos(),
1415 bv()->buffer()->params.textclass,
1417 bv()->setErrorList(el);
1418 bv()->showErrorList(_("Paste"));
1420 redoParagraphs(cursor, endpit);
1422 setCursor(cursor.par(), cursor.pos());
1425 selection.cursor = cursor;
1426 setCursor(ppp.first, ppp.second);
1432 void LyXText::setSelectionRange(lyx::pos_type length)
1437 selection.cursor = cursor;
1444 // simple replacing. The font of the first selected character is used
1445 void LyXText::replaceSelectionWithString(string const & str)
1447 setCursorParUndo(bv());
1450 if (!selection.set()) { // create a dummy selection
1451 selection.end = cursor;
1452 selection.start = cursor;
1455 // Get font setting before we cut
1456 pos_type pos = selection.end.pos();
1457 LyXFont const font = selection.start.par()
1458 ->getFontSettings(bv()->buffer()->params,
1459 selection.start.pos());
1461 // Insert the new string
1462 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1463 selection.end.par()->insertChar(pos, (*cit), font);
1467 // Cut the selection
1468 cutSelection(true, false);
1474 // needed to insert the selection
1475 void LyXText::insertStringAsLines(string const & str)
1477 ParagraphList::iterator pit = cursor.par();
1478 pos_type pos = cursor.pos();
1479 ParagraphList::iterator endpit = boost::next(cursor.par());
1481 setCursorParUndo(bv());
1483 // only to be sure, should not be neccessary
1486 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1488 redoParagraphs(cursor, endpit);
1489 setCursor(cursor.par(), cursor.pos());
1490 selection.cursor = cursor;
1491 setCursor(pit, pos);
1496 // turns double-CR to single CR, others where converted into one
1497 // blank. Then InsertStringAsLines is called
1498 void LyXText::insertStringAsParagraphs(string const & str)
1500 string linestr(str);
1501 bool newline_inserted = false;
1502 for (string::size_type i = 0; i < linestr.length(); ++i) {
1503 if (linestr[i] == '\n') {
1504 if (newline_inserted) {
1505 // we know that \r will be ignored by
1506 // InsertStringA. Of course, it is a dirty
1507 // trick, but it works...
1508 linestr[i - 1] = '\r';
1512 newline_inserted = true;
1514 } else if (IsPrintable(linestr[i])) {
1515 newline_inserted = false;
1518 insertStringAsLines(linestr);
1522 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1524 LyXCursor tmpcursor;
1528 RowList::iterator row = getRow(pit, pos, y);
1529 RowList::iterator beg = rows().begin();
1531 // is there a break one row above
1533 && boost::prior(row)->par() == row->par()) {
1534 z = rowBreakPoint(*boost::prior(row));
1535 if (z >= row->pos()) {
1536 // set the dimensions of the row above
1537 y -= boost::prior(row)->height();
1540 breakAgain(boost::prior(row));
1542 // set the cursor again. Otherwise
1543 // dangling pointers are possible
1544 setCursor(cursor.par(), cursor.pos(),
1545 false, cursor.boundary());
1546 selection.cursor = cursor;
1551 int const tmpheight = row->height();
1552 pos_type const tmplast = lastPos(*this, row);
1555 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1556 postRowPaint(row, y);
1561 // check the special right address boxes
1562 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1569 redoDrawingOfParagraph(tmpcursor);
1572 // set the cursor again. Otherwise dangling pointers are possible
1573 // also set the selection
1575 if (selection.set()) {
1577 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1578 false, selection.cursor.boundary());
1579 selection.cursor = cursor;
1580 setCursorIntern(selection.start.par(),
1581 selection.start.pos(),
1582 false, selection.start.boundary());
1583 selection.start = cursor;
1584 setCursorIntern(selection.end.par(),
1585 selection.end.pos(),
1586 false, selection.end.boundary());
1587 selection.end = cursor;
1588 setCursorIntern(last_sel_cursor.par(),
1589 last_sel_cursor.pos(),
1590 false, last_sel_cursor.boundary());
1591 last_sel_cursor = cursor;
1594 setCursorIntern(cursor.par(), cursor.pos(),
1595 false, cursor.boundary());
1599 // returns false if inset wasn't found
1600 bool LyXText::updateInset(Inset * inset)
1602 // first check the current paragraph
1603 int pos = cursor.par()->getPositionOfInset(inset);
1605 checkParagraph(cursor.par(), pos);
1609 // check every paragraph
1611 ParagraphList::iterator par = ownerParagraphs().begin();
1612 ParagraphList::iterator end = ownerParagraphs().end();
1613 for (; par != end; ++par) {
1614 pos = par->getPositionOfInset(inset);
1616 checkParagraph(par, pos);
1625 bool LyXText::setCursor(ParagraphList::iterator pit,
1627 bool setfont, bool boundary)
1629 LyXCursor old_cursor = cursor;
1630 setCursorIntern(pit, pos, setfont, boundary);
1631 return deleteEmptyParagraphMechanism(old_cursor);
1635 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1636 pos_type pos, bool boundary)
1638 lyx::Assert(pit != ownerParagraphs().end());
1642 cur.boundary(boundary);
1644 // get the cursor y position in text
1646 RowList::iterator row = getRow(pit, pos, y);
1647 RowList::iterator beg = rows().begin();
1649 RowList::iterator old_row = row;
1651 // if we are before the first char of this row and are still in the
1652 // same paragraph and there is a previous row then put the cursor on
1653 // the end of the previous row
1654 cur.iy(y + row->baseline());
1657 boost::prior(row)->par() == row->par() &&
1658 pos < pit->size() &&
1659 pit->getChar(pos) == Paragraph::META_INSET) {
1660 Inset * ins = pit->getInset(pos);
1661 if (ins && (ins->needFullRow() || ins->display())) {
1668 // y is now the beginning of the cursor row
1669 y += row->baseline();
1670 // y is now the cursor baseline
1673 pos_type last = lastPrintablePos(*this, old_row);
1675 // None of these should happen, but we're scaredy-cats
1676 if (pos > pit->size()) {
1677 lyxerr << "dont like 1 please report" << endl;
1680 } else if (pos > last + 1) {
1681 lyxerr << "dont like 2 please report" << endl;
1682 // This shouldn't happen.
1685 } else if (pos < row->pos()) {
1686 lyxerr << "dont like 3 please report" << endl;
1691 // now get the cursors x position
1692 float x = getCursorX(row, pos, last, boundary);
1695 if (old_row != row) {
1696 x = getCursorX(old_row, pos, last, boundary);
1700 /* We take out this for the time being because 1) the redraw code is not
1701 prepared to this yet and 2) because some good policy has yet to be decided
1702 while editting: for instance how to act on rows being created/deleted
1706 //if the cursor is in a visible row, anchor to it
1708 if (topy < y && y < topy + bv()->workHeight())
1714 float LyXText::getCursorX(RowList::iterator rit,
1715 pos_type pos, pos_type last, bool boundary) const
1717 pos_type cursor_vpos = 0;
1719 float fill_separator;
1721 float fill_label_hfill;
1722 // This call HAS to be here because of the BidiTables!!!
1723 prepareToPrint(rit, x, fill_separator, fill_hfill,
1726 ParagraphList::iterator rit_par = rit->par();
1727 pos_type const rit_pos = rit->pos();
1730 cursor_vpos = rit_pos;
1731 else if (pos > last && !boundary)
1732 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1733 ? rit_pos : last + 1;
1734 else if (pos > rit_pos && (pos > last || boundary))
1735 /// Place cursor after char at (logical) position pos - 1
1736 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1737 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1739 /// Place cursor before char at (logical) position pos
1740 cursor_vpos = (bidi_level(pos) % 2 == 0)
1741 ? log2vis(pos) : log2vis(pos) + 1;
1743 pos_type body_pos = rit_par->beginningOfBody();
1744 if ((body_pos > 0) &&
1745 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1748 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1749 pos_type pos = vis2log(vpos);
1750 if (body_pos > 0 && pos == body_pos - 1) {
1751 x += fill_label_hfill +
1752 font_metrics::width(
1753 rit_par->layout()->labelsep,
1754 getLabelFont(bv()->buffer(), rit_par));
1755 if (rit_par->isLineSeparator(body_pos - 1))
1756 x -= singleWidth(rit_par, body_pos - 1);
1759 if (hfillExpansion(*this, rit, pos)) {
1760 x += singleWidth(rit_par, pos);
1761 if (pos >= body_pos)
1764 x += fill_label_hfill;
1765 } else if (rit_par->isSeparator(pos)) {
1766 x += singleWidth(rit_par, pos);
1767 if (pos >= body_pos)
1768 x += fill_separator;
1770 x += singleWidth(rit_par, pos);
1776 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1777 pos_type pos, bool setfont, bool boundary)
1779 InsetText * it = static_cast<InsetText *>(pit->inInset());
1781 if (it != inset_owner) {
1782 lyxerr[Debug::INSETS] << "InsetText is " << it
1784 << "inset_owner is "
1785 << inset_owner << endl;
1786 #ifdef WITH_WARNINGS
1787 #warning I believe this code is wrong. (Lgb)
1788 #warning Jürgen, have a look at this. (Lgb)
1789 #warning Hmmm, I guess you are right but we
1790 #warning should verify when this is needed
1792 // Jürgen, would you like to have a look?
1793 // I guess we need to move the outer cursor
1794 // and open and lock the inset (bla bla bla)
1795 // stuff I don't know... so can you have a look?
1797 // I moved the lyxerr stuff in here so we can see if
1798 // this is actually really needed and where!
1800 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1805 setCursor(cursor, pit, pos, boundary);
1811 void LyXText::setCurrentFont()
1813 pos_type pos = cursor.pos();
1814 ParagraphList::iterator pit = cursor.par();
1816 if (cursor.boundary() && pos > 0)
1820 if (pos == pit->size())
1822 else // potentional bug... BUG (Lgb)
1823 if (pit->isSeparator(pos)) {
1824 if (pos > cursor.row()->pos() &&
1825 bidi_level(pos) % 2 ==
1826 bidi_level(pos - 1) % 2)
1828 else if (pos + 1 < pit->size())
1834 pit->getFontSettings(bv()->buffer()->params, pos);
1835 real_current_font = getFont(bv()->buffer(), pit, pos);
1837 if (cursor.pos() == pit->size() &&
1838 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1839 !cursor.boundary()) {
1840 Language const * lang =
1841 pit->getParLanguage(bv()->buffer()->params);
1842 current_font.setLanguage(lang);
1843 current_font.setNumber(LyXFont::OFF);
1844 real_current_font.setLanguage(lang);
1845 real_current_font.setNumber(LyXFont::OFF);
1850 // returns the column near the specified x-coordinate of the row
1851 // x is set to the real beginning of this column
1853 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1856 float fill_separator;
1858 float fill_label_hfill;
1860 prepareToPrint(rit, tmpx, fill_separator,
1861 fill_hfill, fill_label_hfill);
1863 pos_type vc = rit->pos();
1864 pos_type last = lastPrintablePos(*this, rit);
1867 ParagraphList::iterator rit_par = rit->par();
1868 LyXLayout_ptr const & layout = rit->par()->layout();
1870 bool left_side = false;
1872 pos_type body_pos = rit_par->beginningOfBody();
1873 float last_tmpx = tmpx;
1876 (body_pos - 1 > last ||
1877 !rit_par->isLineSeparator(body_pos - 1)))
1880 // check for empty row
1881 if (!rit_par->size()) {
1886 while (vc <= last && tmpx <= x) {
1889 if (body_pos > 0 && c == body_pos - 1) {
1890 tmpx += fill_label_hfill +
1891 font_metrics::width(layout->labelsep,
1892 getLabelFont(bv()->buffer(), rit_par));
1893 if (rit_par->isLineSeparator(body_pos - 1))
1894 tmpx -= singleWidth(rit_par, body_pos - 1);
1897 if (hfillExpansion(*this, rit, c)) {
1898 tmpx += singleWidth(rit_par, c);
1902 tmpx += fill_label_hfill;
1903 } else if (rit_par->isSeparator(c)) {
1904 tmpx += singleWidth(rit_par, c);
1906 tmpx+= fill_separator;
1908 tmpx += singleWidth(rit_par, c);
1913 if ((tmpx + last_tmpx) / 2 > x) {
1918 if (vc > last + 1) // This shouldn't happen.
1922 // This (rtl_support test) is not needed, but gives
1923 // some speedup if rtl_support=false
1924 bool const lastrow = lyxrc.rtl_support &&
1925 (boost::next(rit) == rowlist_.end() ||
1926 boost::next(rit)->par() != rit_par);
1927 // If lastrow is false, we don't need to compute
1928 // the value of rtl.
1929 bool const rtl = (lastrow)
1930 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1933 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1934 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1936 else if (vc == rit->pos()) {
1938 if (bidi_level(c) % 2 == 1)
1941 c = vis2log(vc - 1);
1942 bool const rtl = (bidi_level(c) % 2 == 1);
1943 if (left_side == rtl) {
1945 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1949 if (rit->pos() <= last && c > last
1950 && rit_par->isNewline(last)) {
1951 if (bidi_level(last) % 2 == 0)
1952 tmpx -= singleWidth(rit_par, last);
1954 tmpx += singleWidth(rit_par, last);
1964 void LyXText::setCursorFromCoordinates(int x, int y)
1966 LyXCursor old_cursor = cursor;
1968 setCursorFromCoordinates(cursor, x, y);
1970 deleteEmptyParagraphMechanism(old_cursor);
1977 * return true if the cursor given is at the end of a row,
1978 * and the next row is filled by an inset that spans an entire
1981 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur) {
1982 RowList::iterator row = cur.row();
1983 if (boost::next(row) == lt.rows().end())
1985 Row const & next = *boost::next(row);
1987 if (next.pos() != cur.pos() || next.par() != cur.par())
1990 if (cur.pos() == cur.par()->size()
1991 || !cur.par()->isInset(cur.pos()))
1993 Inset const * inset = cur.par()->getInset(cur.pos());
1994 if (inset->needFullRow() || inset->display())
2001 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2003 // Get the row first.
2005 RowList::iterator row = getRowNearY(y);
2007 pos_type const column = getColumnNearX(row, x, bound);
2008 cur.par(row->par());
2009 cur.pos(row->pos() + column);
2011 cur.y(y + row->baseline());
2014 if (beforeFullRowInset(*this, cur)) {
2015 pos_type last = lastPrintablePos(*this, row);
2016 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2018 cur.iy(y + row->height() + boost::next(row)->baseline());
2019 cur.irow(boost::next(row));
2025 cur.boundary(bound);
2029 void LyXText::cursorLeft(bool internal)
2031 if (cursor.pos() > 0) {
2032 bool boundary = cursor.boundary();
2033 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2034 if (!internal && !boundary &&
2035 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2036 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2037 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2038 ParagraphList::iterator pit = boost::prior(cursor.par());
2039 setCursor(pit, pit->size());
2044 void LyXText::cursorRight(bool internal)
2046 bool const at_end = (cursor.pos() == cursor.par()->size());
2047 bool const at_newline = !at_end &&
2048 cursor.par()->isNewline(cursor.pos());
2050 if (!internal && cursor.boundary() && !at_newline)
2051 setCursor(cursor.par(), cursor.pos(), true, false);
2053 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2055 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2056 setCursor(cursor.par(), cursor.pos(), true, true);
2057 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2058 setCursor(boost::next(cursor.par()), 0);
2062 void LyXText::cursorUp(bool selecting)
2065 int x = cursor.x_fix();
2066 int y = cursor.y() - cursor.row()->baseline() - 1;
2067 setCursorFromCoordinates(x, y);
2070 int y1 = cursor.iy() - topy;
2073 Inset * inset_hit = checkInsetHit(x, y1);
2074 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2075 inset_hit->localDispatch(
2076 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
2080 setCursorFromCoordinates(bv(), cursor.x_fix(),
2081 cursor.y() - cursor.row()->baseline() - 1);
2086 void LyXText::cursorDown(bool selecting)
2089 int x = cursor.x_fix();
2090 int y = cursor.y() - cursor.row()->baseline() +
2091 cursor.row()->height() + 1;
2092 setCursorFromCoordinates(x, y);
2093 if (!selecting && cursor.row() == cursor.irow()) {
2095 int y1 = cursor.iy() - topy;
2098 Inset * inset_hit = checkInsetHit(x, y1);
2099 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2100 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
2101 inset_hit->localDispatch(cmd);
2105 setCursorFromCoordinates(bv(), cursor.x_fix(),
2106 cursor.y() - cursor.row()->baseline()
2107 + cursor.row()->height() + 1);
2112 void LyXText::cursorUpParagraph()
2114 if (cursor.pos() > 0) {
2115 setCursor(cursor.par(), 0);
2117 else if (cursor.par() != ownerParagraphs().begin()) {
2118 setCursor(boost::prior(cursor.par()), 0);
2123 void LyXText::cursorDownParagraph()
2125 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2126 setCursor(boost::next(cursor.par()), 0);
2128 setCursor(cursor.par(), cursor.par()->size());
2132 // fix the cursor `cur' after a characters has been deleted at `where'
2133 // position. Called by deleteEmptyParagraphMechanism
2134 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2135 LyXCursor const & where)
2137 // if cursor is not in the paragraph where the delete occured,
2139 if (cur.par() != where.par())
2142 // if cursor position is after the place where the delete occured,
2144 if (cur.pos() > where.pos())
2145 cur.pos(cur.pos()-1);
2147 // check also if we don't want to set the cursor on a spot behind the
2148 // pagragraph because we erased the last character.
2149 if (cur.pos() > cur.par()->size())
2150 cur.pos(cur.par()->size());
2152 // recompute row et al. for this cursor
2153 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2157 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2159 // Would be wrong to delete anything if we have a selection.
2160 if (selection.set())
2163 // We allow all kinds of "mumbo-jumbo" when freespacing.
2164 if (old_cursor.par()->layout()->free_spacing
2165 || old_cursor.par()->isFreeSpacing()) {
2169 /* Ok I'll put some comments here about what is missing.
2170 I have fixed BackSpace (and thus Delete) to not delete
2171 double-spaces automagically. I have also changed Cut,
2172 Copy and Paste to hopefully do some sensible things.
2173 There are still some small problems that can lead to
2174 double spaces stored in the document file or space at
2175 the beginning of paragraphs. This happens if you have
2176 the cursor betwenn to spaces and then save. Or if you
2177 cut and paste and the selection have a space at the
2178 beginning and then save right after the paste. I am
2179 sure none of these are very hard to fix, but I will
2180 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2181 that I can get some feedback. (Lgb)
2184 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2185 // delete the LineSeparator.
2188 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2189 // delete the LineSeparator.
2192 // If the pos around the old_cursor were spaces, delete one of them.
2193 if (old_cursor.par() != cursor.par()
2194 || old_cursor.pos() != cursor.pos()) {
2195 // Only if the cursor has really moved
2197 if (old_cursor.pos() > 0
2198 && old_cursor.pos() < old_cursor.par()->size()
2199 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2200 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2201 old_cursor.par()->erase(old_cursor.pos() - 1);
2202 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2204 #ifdef WITH_WARNINGS
2205 #warning This will not work anymore when we have multiple views of the same buffer
2206 // In this case, we will have to correct also the cursors held by
2207 // other bufferviews. It will probably be easier to do that in a more
2208 // automated way in LyXCursor code. (JMarc 26/09/2001)
2210 // correct all cursors held by the LyXText
2211 fixCursorAfterDelete(cursor, old_cursor);
2212 fixCursorAfterDelete(selection.cursor,
2214 fixCursorAfterDelete(selection.start,
2216 fixCursorAfterDelete(selection.end, old_cursor);
2217 fixCursorAfterDelete(last_sel_cursor,
2219 fixCursorAfterDelete(toggle_cursor, old_cursor);
2220 fixCursorAfterDelete(toggle_end_cursor,
2226 // don't delete anything if this is the ONLY paragraph!
2227 if (ownerParagraphs().size() == 1)
2230 // Do not delete empty paragraphs with keepempty set.
2231 if (old_cursor.par()->layout()->keepempty)
2234 // only do our magic if we changed paragraph
2235 if (old_cursor.par() == cursor.par())
2238 // record if we have deleted a paragraph
2239 // we can't possibly have deleted a paragraph before this point
2240 bool deleted = false;
2242 if (old_cursor.par()->empty() ||
2243 (old_cursor.par()->size() == 1 &&
2244 old_cursor.par()->isLineSeparator(0))) {
2245 // ok, we will delete anything
2246 LyXCursor tmpcursor;
2250 bool selection_position_was_oldcursor_position = (
2251 selection.cursor.par() == old_cursor.par()
2252 && selection.cursor.pos() == old_cursor.pos());
2254 if (old_cursor.row() != rows().begin()) {
2256 prevrow = boost::prior(old_cursor.row());
2257 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2259 cursor = old_cursor; // that undo can restore the right cursor position
2260 #warning FIXME. --end() iterator is usable here
2261 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2262 while (endpit != ownerParagraphs().end() &&
2263 endpit->getDepth()) {
2267 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpit);
2271 removeRow(old_cursor.row());
2273 ownerParagraphs().erase(old_cursor.par());
2275 /* Breakagain the next par. Needed because of
2276 * the parindent that can occur or dissappear.
2277 * The next row can change its height, if
2278 * there is another layout before */
2279 if (boost::next(prevrow) != rows().end()) {
2280 breakAgain(boost::next(prevrow));
2283 setHeightOfRow(prevrow);
2285 RowList::iterator nextrow = boost::next(old_cursor.row());
2286 const_cast<LyXText *>(this)->postPaint(
2287 old_cursor.y() - old_cursor.row()->baseline());
2290 cursor = old_cursor; // that undo can restore the right cursor position
2291 #warning FIXME. --end() iterator is usable here
2292 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2293 while (endpit != ownerParagraphs().end() &&
2294 endpit->getDepth()) {
2298 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpit);
2302 removeRow(old_cursor.row());
2304 ownerParagraphs().erase(old_cursor.par());
2306 /* Breakagain the next par. Needed because of
2307 the parindent that can occur or dissappear.
2308 The next row can change its height, if
2309 there is another layout before */
2310 if (nextrow != rows().end()) {
2311 breakAgain(nextrow);
2317 setCursorIntern(cursor.par(), cursor.pos());
2319 if (selection_position_was_oldcursor_position) {
2320 // correct selection
2321 selection.cursor = cursor;
2325 if (old_cursor.par()->stripLeadingSpaces()) {
2326 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2328 setCursorIntern(cursor.par(), cursor.pos());
2329 selection.cursor = cursor;
2336 ParagraphList & LyXText::ownerParagraphs() const
2339 return inset_owner->paragraphs;
2341 return bv_owner->buffer()->paragraphs;
2345 LyXText::refresh_status LyXText::refreshStatus() const
2347 return refresh_status_;
2351 void LyXText::clearPaint()
2353 refresh_status_ = REFRESH_NONE;
2354 refresh_row = rows().end();
2359 void LyXText::postPaint(int start_y)
2361 refresh_status old = refresh_status_;
2363 refresh_status_ = REFRESH_AREA;
2364 refresh_row = rows().end();
2366 if (old != REFRESH_NONE && refresh_y < start_y)
2369 refresh_y = start_y;
2374 // We are an inset's lyxtext. Tell the top-level lyxtext
2375 // it needs to update the row we're in.
2376 LyXText * t = bv()->text;
2377 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2381 // FIXME: we should probably remove this y parameter,
2382 // make refresh_y be 0, and use row->y etc.
2383 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2385 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2386 refresh_status_ = REFRESH_AREA;
2389 refresh_y = start_y;
2392 if (refresh_status_ == REFRESH_AREA)
2395 refresh_status_ = REFRESH_ROW;
2401 // We are an inset's lyxtext. Tell the top-level lyxtext
2402 // it needs to update the row we're in.
2403 LyXText * t = bv()->text;
2404 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2408 bool LyXText::isInInset() const
2410 // Sub-level has non-null bv owner and
2411 // non-null inset owner.
2412 return inset_owner != 0 && bv_owner != 0;
2416 int defaultRowHeight()
2418 LyXFont const font(LyXFont::ALL_SANE);
2419 return int(font_metrics::maxAscent(font)
2420 + font_metrics::maxDescent(font) * 1.5);