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(), boost::prior(undoendpit));
398 // ok we have a selection. This is always between sstart_cur
399 // and sel_end cursor
401 ParagraphList::iterator pit = sstart_cur.par();
402 ParagraphList::iterator epit = boost::next(send_cur.par());
404 LyXLayout_ptr const & lyxlayout =
405 bv()->buffer()->params.getLyXTextClass()[layout];
408 pit->applyLayout(lyxlayout);
409 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
410 ParagraphList::iterator fppit = pit;
411 fppit->params().spaceTop(lyxlayout->fill_top ?
412 VSpace(VSpace::VFILL)
413 : VSpace(VSpace::NONE));
414 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
415 VSpace(VSpace::VFILL)
416 : VSpace(VSpace::NONE));
417 if (lyxlayout->margintype == MARGIN_MANUAL)
418 pit->setLabelWidthString(lyxlayout->labelstring());
421 } while (pit != epit);
427 // set layout over selection and make a total rebreak of those paragraphs
428 void LyXText::setLayout(string const & layout)
430 LyXCursor tmpcursor = cursor; // store the current cursor
432 // if there is no selection just set the layout
433 // of the current paragraph
434 if (!selection.set()) {
435 selection.start = cursor; // dummy selection
436 selection.end = cursor;
439 // special handling of new environment insets
440 BufferParams const & params = bv()->buffer()->params;
441 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
442 if (lyxlayout->is_environment) {
443 // move everything in a new environment inset
444 lyxerr << "setting layout " << layout << endl;
445 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
446 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
447 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
448 Inset * inset = new InsetEnvironment(params, layout);
449 if (bv()->insertInset(inset)) {
451 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
458 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
459 selection.end, layout);
460 redoParagraphs(selection.start, endpit);
462 // we have to reset the selection, because the
463 // geometry could have changed
464 setCursor(selection.start.par(), selection.start.pos(), false);
465 selection.cursor = cursor;
466 setCursor(selection.end.par(), selection.end.pos(), false);
470 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
474 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
476 ParagraphList::iterator pit(cursor.par());
477 ParagraphList::iterator end(cursor.par());
478 ParagraphList::iterator start = pit;
480 if (selection.set()) {
481 pit = selection.start.par();
482 end = selection.end.par();
486 ParagraphList::iterator pastend = boost::next(end);
489 setUndo(bv(), Undo::EDIT, start, end);
491 bool changed = false;
493 int prev_after_depth = 0;
494 #warning parlist ... could be nicer ?
495 if (start != ownerParagraphs().begin()) {
496 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
500 int const depth = pit->params().depth();
501 if (type == bv_funcs::INC_DEPTH) {
502 if (depth < prev_after_depth
503 && pit->layout()->labeltype != LABEL_BIBLIO) {
506 pit->params().depth(depth + 1);
513 pit->params().depth(depth - 1);
516 prev_after_depth = pit->getMaxDepthAfter();
528 // Wow, redoParagraphs is stupid.
530 setCursor(tmpcursor, start, 0);
532 //redoParagraphs(tmpcursor, &(*pastend));
533 redoParagraphs(tmpcursor, pastend);
535 // We need to actually move the text->cursor. I don't
536 // understand why ...
539 // we have to reset the visual selection because the
540 // geometry could have changed
541 if (selection.set()) {
542 setCursor(selection.start.par(), selection.start.pos());
543 selection.cursor = cursor;
544 setCursor(selection.end.par(), selection.end.pos());
547 // this handles the counter labels, and also fixes up
548 // depth values for follow-on (child) paragraphs
552 setCursor(tmpcursor.par(), tmpcursor.pos());
558 // set font over selection and make a total rebreak of those paragraphs
559 void LyXText::setFont(LyXFont const & font, bool toggleall)
561 // if there is no selection just set the current_font
562 if (!selection.set()) {
563 // Determine basis font
565 if (cursor.pos() < cursor.par()->beginningOfBody()) {
566 layoutfont = getLabelFont(bv()->buffer(),
569 layoutfont = getLayoutFont(bv()->buffer(),
572 // Update current font
573 real_current_font.update(font,
574 bv()->buffer()->params.language,
577 // Reduce to implicit settings
578 current_font = real_current_font;
579 current_font.reduce(layoutfont);
580 // And resolve it completely
581 real_current_font.realize(layoutfont);
586 LyXCursor tmpcursor = cursor; // store the current cursor
588 // ok we have a selection. This is always between sel_start_cursor
589 // and sel_end cursor
591 setUndo(bv(), Undo::EDIT, selection.start.par(), selection.end.par());
593 cursor = selection.start;
594 while (cursor.par() != selection.end.par() ||
595 cursor.pos() < selection.end.pos())
597 if (cursor.pos() < cursor.par()->size()) {
598 // an open footnote should behave like a closed one
599 setCharFont(cursor.par(), cursor.pos(),
601 cursor.pos(cursor.pos() + 1);
604 cursor.par(boost::next(cursor.par()));
609 redoParagraphs(selection.start, boost::next(selection.end.par()));
611 // we have to reset the selection, because the
612 // geometry could have changed, but we keep
613 // it for user convenience
614 setCursor(selection.start.par(), selection.start.pos());
615 selection.cursor = cursor;
616 setCursor(selection.end.par(), selection.end.pos());
618 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
619 tmpcursor.boundary());
623 void LyXText::redoHeightOfParagraph()
625 RowList::iterator tmprow = cursor.row();
626 int y = cursor.y() - tmprow->baseline();
628 setHeightOfRow(tmprow);
630 while (tmprow != rows().begin()
631 && boost::prior(tmprow)->par() == tmprow->par()) {
633 y -= tmprow->height();
634 setHeightOfRow(tmprow);
639 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
643 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
645 RowList::iterator tmprow = cur.row();
647 int y = cur.y() - tmprow->baseline();
648 setHeightOfRow(tmprow);
650 while (tmprow != rows().begin()
651 && boost::prior(tmprow)->par() == tmprow->par()) {
653 y -= tmprow->height();
657 setCursor(cur.par(), cur.pos());
661 // deletes and inserts again all paragaphs between the cursor
662 // and the specified par
663 // This function is needed after SetLayout and SetFont etc.
664 void LyXText::redoParagraphs(LyXCursor const & cur,
665 ParagraphList::iterator endpit)
667 RowList::iterator tmprit = cur.row();
668 int y = cur.y() - tmprit->baseline();
670 ParagraphList::iterator first_phys_pit;
671 RowList::iterator prevrit;
672 if (tmprit == rows().begin()) {
673 // A trick/hack for UNDO.
674 // This is needed because in an UNDO/REDO we could have
675 // changed the ownerParagrah() so the paragraph inside
676 // the row is NOT my really first par anymore.
677 // Got it Lars ;) (Jug 20011206)
678 first_phys_pit = ownerParagraphs().begin();
679 prevrit = rows().end();
681 first_phys_pit = tmprit->par();
682 while (tmprit != rows().begin()
683 && boost::prior(tmprit)->par() == first_phys_pit)
686 y -= tmprit->height();
688 prevrit = boost::prior(tmprit);
692 while (tmprit != rows().end() && tmprit->par() != endpit) {
693 RowList::iterator tmprit2 = tmprit++;
697 // Reinsert the paragraphs.
698 ParagraphList::iterator tmppit = first_phys_pit;
700 while (tmppit != ownerParagraphs().end()) {
701 insertParagraph(tmppit, tmprit);
702 while (tmprit != rows().end()
703 && tmprit->par() == tmppit) {
707 if (tmppit == endpit)
710 if (prevrit != rows().end()) {
711 setHeightOfRow(prevrit);
712 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
714 setHeightOfRow(rows().begin());
715 const_cast<LyXText *>(this)->postPaint(0);
717 if (tmprit != rows().end())
718 setHeightOfRow(tmprit);
724 void LyXText::fullRebreak()
726 if (rows().empty()) {
731 RowList::iterator rows_end = rows().end();
733 if (need_break_row != rows_end) {
734 breakAgain(need_break_row);
735 need_break_row = rows_end;
741 // important for the screen
744 // the cursor set functions have a special mechanism. When they
745 // realize, that you left an empty paragraph, they will delete it.
746 // They also delete the corresponding row
748 // need the selection cursor:
749 void LyXText::setSelection()
751 bool const lsel = selection.set();
753 if (!selection.set()) {
754 last_sel_cursor = selection.cursor;
755 selection.start = selection.cursor;
756 selection.end = selection.cursor;
761 // first the toggling area
762 if (cursor.y() < last_sel_cursor.y()
763 || (cursor.y() == last_sel_cursor.y()
764 && cursor.x() < last_sel_cursor.x())) {
765 toggle_end_cursor = last_sel_cursor;
766 toggle_cursor = cursor;
768 toggle_end_cursor = cursor;
769 toggle_cursor = last_sel_cursor;
772 last_sel_cursor = cursor;
774 // and now the whole selection
776 if (selection.cursor.par() == cursor.par())
777 if (selection.cursor.pos() < cursor.pos()) {
778 selection.end = cursor;
779 selection.start = selection.cursor;
781 selection.end = selection.cursor;
782 selection.start = cursor;
784 else if (selection.cursor.y() < cursor.y() ||
785 (selection.cursor.y() == cursor.y()
786 && selection.cursor.x() < cursor.x())) {
787 selection.end = cursor;
788 selection.start = selection.cursor;
791 selection.end = selection.cursor;
792 selection.start = cursor;
795 // a selection with no contents is not a selection
796 if (selection.start.par() == selection.end.par() &&
797 selection.start.pos() == selection.end.pos())
798 selection.set(false);
800 if (inset_owner && (selection.set() || lsel))
801 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
805 string const LyXText::selectionAsString(Buffer const * buffer,
808 if (!selection.set()) return string();
810 // should be const ...
811 ParagraphList::iterator startpit = selection.start.par();
812 ParagraphList::iterator endpit = selection.end.par();
813 pos_type const startpos(selection.start.pos());
814 pos_type const endpos(selection.end.pos());
816 if (startpit == endpit) {
817 return startpit->asString(buffer, startpos, endpos, label);
822 // First paragraph in selection
823 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
825 // The paragraphs in between (if any)
826 ParagraphList::iterator pit = boost::next(startpit);
827 for (; pit != endpit; ++pit) {
828 result += pit->asString(buffer, 0, pit->size(), label) + "\n\n";
831 // Last paragraph in selection
832 result += endpit->asString(buffer, 0, endpos, label);
838 void LyXText::clearSelection()
840 selection.set(false);
841 selection.mark(false);
842 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
843 // reset this in the bv_owner!
844 if (bv_owner && bv_owner->text)
845 bv_owner->text->xsel_cache.set(false);
849 void LyXText::cursorHome()
851 setCursor(cursor.par(), cursor.row()->pos());
855 void LyXText::cursorEnd()
857 if (cursor.par()->empty())
860 RowList::iterator rit = cursor.row();
861 RowList::iterator next_rit = boost::next(rit);
862 ParagraphList::iterator pit = rit->par();
863 pos_type last_pos = lastPos(*this, rit);
865 if (next_rit == rows().end() || next_rit->par() != pit) {
869 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
874 setCursor(pit, last_pos);
878 void LyXText::cursorTop()
880 setCursor(ownerParagraphs().begin(), 0);
884 void LyXText::cursorBottom()
887 // This is how it should be:
889 ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
891 ParagraphList::iterator lastpit = ownerParagraphs().begin();
892 ParagraphList::iterator end = ownerParagraphs().end();
893 while (boost::next(lastpit) != end)
896 int pos = lastpit->size();
897 setCursor(lastpit, pos);
901 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
903 // If the mask is completely neutral, tell user
904 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
905 // Could only happen with user style
906 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
910 // Try implicit word selection
911 // If there is a change in the language the implicit word selection
913 LyXCursor resetCursor = cursor;
914 bool implicitSelection = (font.language() == ignore_language
915 && font.number() == LyXFont::IGNORE)
916 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
919 setFont(font, toggleall);
921 // Implicit selections are cleared afterwards
922 //and cursor is set to the original position.
923 if (implicitSelection) {
925 cursor = resetCursor;
926 setCursor(cursor.par(), cursor.pos());
927 selection.cursor = cursor;
930 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
934 string LyXText::getStringToIndex()
936 // Try implicit word selection
937 // If there is a change in the language the implicit word selection
939 LyXCursor const reset_cursor = cursor;
940 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
943 if (!selection.set())
944 bv()->owner()->message(_("Nothing to index!"));
945 else if (selection.start.par() != selection.end.par())
946 bv()->owner()->message(_("Cannot index more than one paragraph!"));
948 idxstring = selectionAsString(bv()->buffer(), false);
950 // Reset cursors to their original position.
951 cursor = reset_cursor;
952 setCursor(cursor.par(), cursor.pos());
953 selection.cursor = cursor;
955 // Clear the implicit selection.
956 if (implicitSelection)
963 // the DTP switches for paragraphs. LyX will store them in the first
964 // physicla paragraph. When a paragraph is broken, the top settings rest,
965 // the bottom settings are given to the new one. So I can make shure,
966 // they do not duplicate themself and you cannnot make dirty things with
969 void LyXText::setParagraph(bool line_top, bool line_bottom,
970 bool pagebreak_top, bool pagebreak_bottom,
971 VSpace const & space_top,
972 VSpace const & space_bottom,
973 Spacing const & spacing,
975 string const & labelwidthstring,
978 LyXCursor tmpcursor = cursor;
979 if (!selection.set()) {
980 selection.start = cursor;
981 selection.end = cursor;
984 // make sure that the depth behind the selection are restored, too
985 ParagraphList::iterator endpit = boost::next(selection.end.par());
986 ParagraphList::iterator undoendpit = endpit;
987 ParagraphList::iterator pars_end = ownerParagraphs().end();
989 if (endpit != pars_end && endpit->getDepth()) {
990 while (endpit != pars_end && endpit->getDepth()) {
994 } else if (endpit != pars_end) {
995 // because of parindents etc.
999 setUndo(bv(), Undo::EDIT, selection.start.par(),
1000 boost::prior(undoendpit));
1003 ParagraphList::iterator tmppit = selection.end.par();
1005 while (tmppit != boost::prior(selection.start.par())) {
1006 setCursor(tmppit, 0);
1007 postPaint(cursor.y() - cursor.row()->baseline());
1009 ParagraphList::iterator pit = cursor.par();
1010 ParagraphParameters & params = pit->params();
1012 params.lineTop(line_top);
1013 params.lineBottom(line_bottom);
1014 params.pagebreakTop(pagebreak_top);
1015 params.pagebreakBottom(pagebreak_bottom);
1016 params.spaceTop(space_top);
1017 params.spaceBottom(space_bottom);
1018 params.spacing(spacing);
1019 // does the layout allow the new alignment?
1020 LyXLayout_ptr const & layout = pit->layout();
1022 if (align == LYX_ALIGN_LAYOUT)
1023 align = layout->align;
1024 if (align & layout->alignpossible) {
1025 if (align == layout->align)
1026 params.align(LYX_ALIGN_LAYOUT);
1028 params.align(align);
1030 pit->setLabelWidthString(labelwidthstring);
1031 params.noindent(noindent);
1032 tmppit = boost::prior(pit);
1035 redoParagraphs(selection.start, endpit);
1038 setCursor(selection.start.par(), selection.start.pos());
1039 selection.cursor = cursor;
1040 setCursor(selection.end.par(), selection.end.pos());
1042 setCursor(tmpcursor.par(), tmpcursor.pos());
1044 bv()->updateInset(inset_owner);
1048 // set the counter of a paragraph. This includes the labels
1049 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1051 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1052 LyXLayout_ptr const & layout = pit->layout();
1054 if (pit != ownerParagraphs().begin()) {
1056 pit->params().appendix(boost::prior(pit)->params().appendix());
1057 if (!pit->params().appendix() &&
1058 pit->params().startOfAppendix()) {
1059 pit->params().appendix(true);
1060 textclass.counters().reset();
1062 pit->enumdepth = boost::prior(pit)->enumdepth;
1063 pit->itemdepth = boost::prior(pit)->itemdepth;
1065 pit->params().appendix(pit->params().startOfAppendix());
1070 /* Maybe we have to increment the enumeration depth.
1071 * BUT, enumeration in a footnote is considered in isolation from its
1072 * surrounding paragraph so don't increment if this is the
1073 * first line of the footnote
1074 * AND, bibliographies can't have their depth changed ie. they
1075 * are always of depth 0
1077 if (pit != ownerParagraphs().begin()
1078 && boost::prior(pit)->getDepth() < pit->getDepth()
1079 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1080 && pit->enumdepth < 3
1081 && layout->labeltype != LABEL_BIBLIO) {
1085 // Maybe we have to decrement the enumeration depth, see note above
1086 if (pit != ownerParagraphs().begin()
1087 && boost::prior(pit)->getDepth() > pit->getDepth()
1088 && layout->labeltype != LABEL_BIBLIO) {
1089 pit->enumdepth = depthHook(pit, ownerParagraphs(),
1090 pit->getDepth())->enumdepth;
1093 if (!pit->params().labelString().empty()) {
1094 pit->params().labelString(string());
1097 if (layout->margintype == MARGIN_MANUAL) {
1098 if (pit->params().labelWidthString().empty()) {
1099 pit->setLabelWidthString(layout->labelstring());
1102 pit->setLabelWidthString(string());
1105 // is it a layout that has an automatic label?
1106 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1107 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1111 if (i >= 0 && i <= buf->params.secnumdepth) {
1115 textclass.counters().step(layout->latexname());
1117 // Is there a label? Useful for Chapter layout
1118 if (!pit->params().appendix()) {
1119 s << buf->B_(layout->labelstring());
1121 s << buf->B_(layout->labelstring_appendix());
1124 // Use of an integer is here less than elegant. For now.
1125 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1126 if (!pit->params().appendix()) {
1127 numbertype = "sectioning";
1129 numbertype = "appendix";
1130 if (pit->isRightToLeftPar(buf->params))
1131 langtype = "hebrew";
1137 << textclass.counters()
1138 .numberLabel(layout->latexname(),
1139 numbertype, langtype, head);
1141 pit->params().labelString(STRCONV(s.str()));
1143 // reset enum counters
1144 textclass.counters().reset("enum");
1145 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1146 textclass.counters().reset("enum");
1147 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1149 // Yes I know this is a really, really! bad solution
1151 string enumcounter("enum");
1153 switch (pit->enumdepth) {
1162 enumcounter += "iv";
1165 // not a valid enumdepth...
1169 textclass.counters().step(enumcounter);
1171 s << textclass.counters()
1172 .numberLabel(enumcounter, "enumeration");
1173 pit->params().labelString(STRCONV(s.str()));
1175 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1176 textclass.counters().step("bibitem");
1177 int number = textclass.counters().value("bibitem");
1178 if (pit->bibitem()) {
1179 pit->bibitem()->setCounter(number);
1180 pit->params().labelString(layout->labelstring());
1182 // In biblio should't be following counters but...
1184 string s = buf->B_(layout->labelstring());
1186 // the caption hack:
1187 if (layout->labeltype == LABEL_SENSITIVE) {
1188 ParagraphList::iterator tmppit = pit;
1191 while (tmppit != ownerParagraphs().end() &&
1193 // the single '=' is intended below
1194 && (in = tmppit->inInset()->owner())) {
1195 if (in->lyxCode() == Inset::FLOAT_CODE ||
1196 in->lyxCode() == Inset::WRAP_CODE) {
1200 tmppit = ownerParagraphs().find(*in->parOwner());
1207 if (in->lyxCode() == Inset::FLOAT_CODE)
1208 type = static_cast<InsetFloat*>(in)->params().type;
1209 else if (in->lyxCode() == Inset::WRAP_CODE)
1210 type = static_cast<InsetWrap*>(in)->params().type;
1214 Floating const & fl = textclass.floats().getType(type);
1216 textclass.counters().step(fl.type());
1218 // Doesn't work... yet.
1219 s = bformat(_("%1$s #:"), buf->B_(fl.name()));
1221 // par->SetLayout(0);
1222 // s = layout->labelstring;
1223 s = _("Senseless: ");
1226 pit->params().labelString(s);
1228 // reset the enumeration counter. They are always reset
1229 // when there is any other layout between
1230 // Just fall-through between the cases so that all
1231 // enum counters deeper than enumdepth is also reset.
1232 switch (pit->enumdepth) {
1234 textclass.counters().reset("enumi");
1236 textclass.counters().reset("enumii");
1238 textclass.counters().reset("enumiii");
1240 textclass.counters().reset("enumiv");
1246 // Updates all counters. Paragraphs with changed label string will be rebroken
1247 void LyXText::updateCounters()
1249 RowList::iterator rowit = rows().begin();
1250 ParagraphList::iterator pit = rowit->par();
1252 // CHECK if this is really needed. (Lgb)
1253 bv()->buffer()->params.getLyXTextClass().counters().reset();
1255 for (; pit != ownerParagraphs().end(); ++pit) {
1256 while (rowit->par() != pit)
1259 string const oldLabel = pit->params().labelString();
1261 size_t maxdepth = 0;
1262 if (pit != ownerParagraphs().begin())
1263 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1265 if (pit->params().depth() > maxdepth)
1266 pit->params().depth(maxdepth);
1268 // setCounter can potentially change the labelString.
1269 setCounter(bv()->buffer(), pit);
1271 string const & newLabel = pit->params().labelString();
1273 if (oldLabel.empty() && !newLabel.empty()) {
1274 removeParagraph(rowit);
1275 appendParagraph(rowit);
1281 void LyXText::insertInset(Inset * inset)
1283 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1285 setUndo(bv(), Undo::FINISH, cursor.par());
1287 cursor.par()->insertInset(cursor.pos(), inset);
1288 // Just to rebreak and refresh correctly.
1289 // The character will not be inserted a second time
1290 insertChar(Paragraph::META_INSET);
1291 // If we enter a highly editable inset the cursor should be to before
1292 // the inset. This couldn't happen before as Undo was not handled inside
1293 // inset now after the Undo LyX tries to call inset->Edit(...) again
1294 // and cannot do this as the cursor is behind the inset and GetInset
1295 // does not return the inset!
1296 if (isHighlyEditableInset(inset)) {
1303 void LyXText::cutSelection(bool doclear, bool realcut)
1305 // Stuff what we got on the clipboard. Even if there is no selection.
1307 // There is a problem with having the stuffing here in that the
1308 // larger the selection the slower LyX will get. This can be
1309 // solved by running the line below only when the selection has
1310 // finished. The solution used currently just works, to make it
1311 // faster we need to be more clever and probably also have more
1312 // calls to stuffClipboard. (Lgb)
1313 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1315 // This doesn't make sense, if there is no selection
1316 if (!selection.set())
1319 // OK, we have a selection. This is always between selection.start
1320 // and selection.end
1322 // make sure that the depth behind the selection are restored, too
1323 ParagraphList::iterator endpit = boost::next(selection.end.par());
1324 ParagraphList::iterator undoendpit = endpit;
1325 ParagraphList::iterator pars_end = ownerParagraphs().end();
1327 if (endpit != pars_end && endpit->getDepth()) {
1328 while (endpit != pars_end && endpit->getDepth()) {
1330 undoendpit = endpit;
1332 } else if (endpit != pars_end) {
1333 // because of parindents etc.
1337 setUndo(bv(), Undo::DELETE, selection.start.par(),
1338 boost::prior(undoendpit));
1341 endpit = selection.end.par();
1342 int endpos = selection.end.pos();
1344 boost::tie(endpit, endpos) = realcut ?
1345 CutAndPaste::cutSelection(ownerParagraphs(),
1346 selection.start.par(), endpit,
1347 selection.start.pos(), endpos,
1348 bv()->buffer()->params.textclass,
1350 : CutAndPaste::eraseSelection(ownerParagraphs(),
1351 selection.start.par(), endpit,
1352 selection.start.pos(), endpos,
1354 // sometimes necessary
1356 selection.start.par()->stripLeadingSpaces();
1358 redoParagraphs(selection.start, boost::next(endpit));
1359 #warning FIXME latent bug
1360 // endpit will be invalidated on redoParagraphs once ParagraphList
1361 // becomes a std::list? There are maybe other places on which this
1362 // can happend? (Ab)
1363 // cutSelection can invalidate the cursor so we need to set
1365 // we prefer the end for when tracking changes
1369 // need a valid cursor. (Lgb)
1372 setCursor(cursor.par(), cursor.pos());
1373 selection.cursor = cursor;
1378 void LyXText::copySelection()
1380 // stuff the selection onto the X clipboard, from an explicit copy request
1381 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1383 // this doesnt make sense, if there is no selection
1384 if (!selection.set())
1387 // ok we have a selection. This is always between selection.start
1388 // and sel_end cursor
1390 // copy behind a space if there is one
1391 while (selection.start.par()->size() > selection.start.pos()
1392 && selection.start.par()->isLineSeparator(selection.start.pos())
1393 && (selection.start.par() != selection.end.par()
1394 || selection.start.pos() < selection.end.pos()))
1395 selection.start.pos(selection.start.pos() + 1);
1397 CutAndPaste::copySelection(selection.start.par(),
1398 selection.end.par(),
1399 selection.start.pos(), selection.end.pos(),
1400 bv()->buffer()->params.textclass);
1404 void LyXText::pasteSelection()
1406 // this does not make sense, if there is nothing to paste
1407 if (!CutAndPaste::checkPastePossible())
1410 setUndo(bv(), Undo::INSERT, cursor.par());
1412 ParagraphList::iterator endpit;
1417 boost::tie(ppp, endpit) =
1418 CutAndPaste::pasteSelection(ownerParagraphs(),
1419 cursor.par(), cursor.pos(),
1420 bv()->buffer()->params.textclass,
1422 bv()->setErrorList(el);
1423 bv()->showErrorList(_("Paste"));
1425 redoParagraphs(cursor, endpit);
1427 setCursor(cursor.par(), cursor.pos());
1430 selection.cursor = cursor;
1431 setCursor(ppp.first, ppp.second);
1437 void LyXText::setSelectionRange(lyx::pos_type length)
1442 selection.cursor = cursor;
1449 // simple replacing. The font of the first selected character is used
1450 void LyXText::replaceSelectionWithString(string const & str)
1452 setCursorParUndo(bv());
1455 if (!selection.set()) { // create a dummy selection
1456 selection.end = cursor;
1457 selection.start = cursor;
1460 // Get font setting before we cut
1461 pos_type pos = selection.end.pos();
1462 LyXFont const font = selection.start.par()
1463 ->getFontSettings(bv()->buffer()->params,
1464 selection.start.pos());
1466 // Insert the new string
1467 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1468 selection.end.par()->insertChar(pos, (*cit), font);
1472 // Cut the selection
1473 cutSelection(true, false);
1479 // needed to insert the selection
1480 void LyXText::insertStringAsLines(string const & str)
1482 ParagraphList::iterator pit = cursor.par();
1483 pos_type pos = cursor.pos();
1484 ParagraphList::iterator endpit = boost::next(cursor.par());
1486 setCursorParUndo(bv());
1488 // only to be sure, should not be neccessary
1491 bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1493 redoParagraphs(cursor, endpit);
1494 setCursor(cursor.par(), cursor.pos());
1495 selection.cursor = cursor;
1496 setCursor(pit, pos);
1501 // turns double-CR to single CR, others where converted into one
1502 // blank. Then InsertStringAsLines is called
1503 void LyXText::insertStringAsParagraphs(string const & str)
1505 string linestr(str);
1506 bool newline_inserted = false;
1507 for (string::size_type i = 0; i < linestr.length(); ++i) {
1508 if (linestr[i] == '\n') {
1509 if (newline_inserted) {
1510 // we know that \r will be ignored by
1511 // InsertStringA. Of course, it is a dirty
1512 // trick, but it works...
1513 linestr[i - 1] = '\r';
1517 newline_inserted = true;
1519 } else if (IsPrintable(linestr[i])) {
1520 newline_inserted = false;
1523 insertStringAsLines(linestr);
1527 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1529 LyXCursor tmpcursor;
1533 RowList::iterator row = getRow(pit, pos, y);
1534 RowList::iterator beg = rows().begin();
1536 // is there a break one row above
1538 && boost::prior(row)->par() == row->par()) {
1539 z = rowBreakPoint(*boost::prior(row));
1540 if (z >= row->pos()) {
1541 // set the dimensions of the row above
1542 y -= boost::prior(row)->height();
1545 breakAgain(boost::prior(row));
1547 // set the cursor again. Otherwise
1548 // dangling pointers are possible
1549 setCursor(cursor.par(), cursor.pos(),
1550 false, cursor.boundary());
1551 selection.cursor = cursor;
1556 int const tmpheight = row->height();
1557 pos_type const tmplast = lastPos(*this, row);
1560 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1561 postRowPaint(row, y);
1566 // check the special right address boxes
1567 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1574 redoDrawingOfParagraph(tmpcursor);
1577 // set the cursor again. Otherwise dangling pointers are possible
1578 // also set the selection
1580 if (selection.set()) {
1582 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1583 false, selection.cursor.boundary());
1584 selection.cursor = cursor;
1585 setCursorIntern(selection.start.par(),
1586 selection.start.pos(),
1587 false, selection.start.boundary());
1588 selection.start = cursor;
1589 setCursorIntern(selection.end.par(),
1590 selection.end.pos(),
1591 false, selection.end.boundary());
1592 selection.end = cursor;
1593 setCursorIntern(last_sel_cursor.par(),
1594 last_sel_cursor.pos(),
1595 false, last_sel_cursor.boundary());
1596 last_sel_cursor = cursor;
1599 setCursorIntern(cursor.par(), cursor.pos(),
1600 false, cursor.boundary());
1604 // returns false if inset wasn't found
1605 bool LyXText::updateInset(Inset * inset)
1607 // first check the current paragraph
1608 int pos = cursor.par()->getPositionOfInset(inset);
1610 checkParagraph(cursor.par(), pos);
1614 // check every paragraph
1616 ParagraphList::iterator par = ownerParagraphs().begin();
1617 ParagraphList::iterator end = ownerParagraphs().end();
1618 for (; par != end; ++par) {
1619 pos = par->getPositionOfInset(inset);
1621 checkParagraph(par, pos);
1630 bool LyXText::setCursor(ParagraphList::iterator pit,
1632 bool setfont, bool boundary)
1634 LyXCursor old_cursor = cursor;
1635 setCursorIntern(pit, pos, setfont, boundary);
1636 return deleteEmptyParagraphMechanism(old_cursor);
1640 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1641 pos_type pos, bool boundary)
1643 lyx::Assert(pit != ownerParagraphs().end());
1647 cur.boundary(boundary);
1649 // get the cursor y position in text
1651 RowList::iterator row = getRow(pit, pos, y);
1652 RowList::iterator beg = rows().begin();
1654 RowList::iterator old_row = row;
1656 // if we are before the first char of this row and are still in the
1657 // same paragraph and there is a previous row then put the cursor on
1658 // the end of the previous row
1659 cur.iy(y + row->baseline());
1662 boost::prior(row)->par() == row->par() &&
1663 pos < pit->size() &&
1664 pit->getChar(pos) == Paragraph::META_INSET) {
1665 Inset * ins = pit->getInset(pos);
1666 if (ins && (ins->needFullRow() || ins->display())) {
1673 // y is now the beginning of the cursor row
1674 y += row->baseline();
1675 // y is now the cursor baseline
1678 pos_type last = lastPrintablePos(*this, old_row);
1680 // None of these should happen, but we're scaredy-cats
1681 if (pos > pit->size()) {
1682 lyxerr << "dont like 1 please report" << endl;
1685 } else if (pos > last + 1) {
1686 lyxerr << "dont like 2 please report" << endl;
1687 // This shouldn't happen.
1690 } else if (pos < row->pos()) {
1691 lyxerr << "dont like 3 please report" << endl;
1696 // now get the cursors x position
1697 float x = getCursorX(row, pos, last, boundary);
1700 if (old_row != row) {
1701 x = getCursorX(old_row, pos, last, boundary);
1705 /* We take out this for the time being because 1) the redraw code is not
1706 prepared to this yet and 2) because some good policy has yet to be decided
1707 while editting: for instance how to act on rows being created/deleted
1711 //if the cursor is in a visible row, anchor to it
1713 if (topy < y && y < topy + bv()->workHeight())
1719 float LyXText::getCursorX(RowList::iterator rit,
1720 pos_type pos, pos_type last, bool boundary) const
1722 pos_type cursor_vpos = 0;
1724 float fill_separator;
1726 float fill_label_hfill;
1727 // This call HAS to be here because of the BidiTables!!!
1728 prepareToPrint(rit, x, fill_separator, fill_hfill,
1731 ParagraphList::iterator rit_par = rit->par();
1732 pos_type const rit_pos = rit->pos();
1735 cursor_vpos = rit_pos;
1736 else if (pos > last && !boundary)
1737 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1738 ? rit_pos : last + 1;
1739 else if (pos > rit_pos && (pos > last || boundary))
1740 /// Place cursor after char at (logical) position pos - 1
1741 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1742 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1744 /// Place cursor before char at (logical) position pos
1745 cursor_vpos = (bidi_level(pos) % 2 == 0)
1746 ? log2vis(pos) : log2vis(pos) + 1;
1748 pos_type body_pos = rit_par->beginningOfBody();
1749 if ((body_pos > 0) &&
1750 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1753 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1754 pos_type pos = vis2log(vpos);
1755 if (body_pos > 0 && pos == body_pos - 1) {
1756 x += fill_label_hfill +
1757 font_metrics::width(
1758 rit_par->layout()->labelsep,
1759 getLabelFont(bv()->buffer(), rit_par));
1760 if (rit_par->isLineSeparator(body_pos - 1))
1761 x -= singleWidth(rit_par, body_pos - 1);
1764 if (hfillExpansion(*this, rit, pos)) {
1765 x += singleWidth(rit_par, pos);
1766 if (pos >= body_pos)
1769 x += fill_label_hfill;
1770 } else if (rit_par->isSeparator(pos)) {
1771 x += singleWidth(rit_par, pos);
1772 if (pos >= body_pos)
1773 x += fill_separator;
1775 x += singleWidth(rit_par, pos);
1781 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1782 pos_type pos, bool setfont, bool boundary)
1784 InsetText * it = static_cast<InsetText *>(pit->inInset());
1786 if (it != inset_owner) {
1787 lyxerr[Debug::INSETS] << "InsetText is " << it
1789 << "inset_owner is "
1790 << inset_owner << endl;
1791 #ifdef WITH_WARNINGS
1792 #warning I believe this code is wrong. (Lgb)
1793 #warning Jürgen, have a look at this. (Lgb)
1794 #warning Hmmm, I guess you are right but we
1795 #warning should verify when this is needed
1797 // Jürgen, would you like to have a look?
1798 // I guess we need to move the outer cursor
1799 // and open and lock the inset (bla bla bla)
1800 // stuff I don't know... so can you have a look?
1802 // I moved the lyxerr stuff in here so we can see if
1803 // this is actually really needed and where!
1805 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1810 setCursor(cursor, pit, pos, boundary);
1816 void LyXText::setCurrentFont()
1818 pos_type pos = cursor.pos();
1819 ParagraphList::iterator pit = cursor.par();
1821 if (cursor.boundary() && pos > 0)
1825 if (pos == pit->size())
1827 else // potentional bug... BUG (Lgb)
1828 if (pit->isSeparator(pos)) {
1829 if (pos > cursor.row()->pos() &&
1830 bidi_level(pos) % 2 ==
1831 bidi_level(pos - 1) % 2)
1833 else if (pos + 1 < pit->size())
1839 pit->getFontSettings(bv()->buffer()->params, pos);
1840 real_current_font = getFont(bv()->buffer(), pit, pos);
1842 if (cursor.pos() == pit->size() &&
1843 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1844 !cursor.boundary()) {
1845 Language const * lang =
1846 pit->getParLanguage(bv()->buffer()->params);
1847 current_font.setLanguage(lang);
1848 current_font.setNumber(LyXFont::OFF);
1849 real_current_font.setLanguage(lang);
1850 real_current_font.setNumber(LyXFont::OFF);
1855 // returns the column near the specified x-coordinate of the row
1856 // x is set to the real beginning of this column
1858 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1861 float fill_separator;
1863 float fill_label_hfill;
1865 prepareToPrint(rit, tmpx, fill_separator,
1866 fill_hfill, fill_label_hfill);
1868 pos_type vc = rit->pos();
1869 pos_type last = lastPrintablePos(*this, rit);
1872 ParagraphList::iterator rit_par = rit->par();
1873 LyXLayout_ptr const & layout = rit->par()->layout();
1875 bool left_side = false;
1877 pos_type body_pos = rit_par->beginningOfBody();
1878 float last_tmpx = tmpx;
1881 (body_pos - 1 > last ||
1882 !rit_par->isLineSeparator(body_pos - 1)))
1885 // check for empty row
1886 if (!rit_par->size()) {
1891 while (vc <= last && tmpx <= x) {
1894 if (body_pos > 0 && c == body_pos - 1) {
1895 tmpx += fill_label_hfill +
1896 font_metrics::width(layout->labelsep,
1897 getLabelFont(bv()->buffer(), rit_par));
1898 if (rit_par->isLineSeparator(body_pos - 1))
1899 tmpx -= singleWidth(rit_par, body_pos - 1);
1902 if (hfillExpansion(*this, rit, c)) {
1903 tmpx += singleWidth(rit_par, c);
1907 tmpx += fill_label_hfill;
1908 } else if (rit_par->isSeparator(c)) {
1909 tmpx += singleWidth(rit_par, c);
1911 tmpx+= fill_separator;
1913 tmpx += singleWidth(rit_par, c);
1918 if ((tmpx + last_tmpx) / 2 > x) {
1923 if (vc > last + 1) // This shouldn't happen.
1927 // This (rtl_support test) is not needed, but gives
1928 // some speedup if rtl_support=false
1929 bool const lastrow = lyxrc.rtl_support &&
1930 (boost::next(rit) == rowlist_.end() ||
1931 boost::next(rit)->par() != rit_par);
1932 // If lastrow is false, we don't need to compute
1933 // the value of rtl.
1934 bool const rtl = (lastrow)
1935 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1938 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1939 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1941 else if (vc == rit->pos()) {
1943 if (bidi_level(c) % 2 == 1)
1946 c = vis2log(vc - 1);
1947 bool const rtl = (bidi_level(c) % 2 == 1);
1948 if (left_side == rtl) {
1950 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1954 if (rit->pos() <= last && c > last
1955 && rit_par->isNewline(last)) {
1956 if (bidi_level(last) % 2 == 0)
1957 tmpx -= singleWidth(rit_par, last);
1959 tmpx += singleWidth(rit_par, last);
1969 void LyXText::setCursorFromCoordinates(int x, int y)
1971 LyXCursor old_cursor = cursor;
1973 setCursorFromCoordinates(cursor, x, y);
1975 deleteEmptyParagraphMechanism(old_cursor);
1982 * return true if the cursor given is at the end of a row,
1983 * and the next row is filled by an inset that spans an entire
1986 bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur) {
1987 RowList::iterator row = cur.row();
1988 if (boost::next(row) == lt.rows().end())
1990 Row const & next = *boost::next(row);
1992 if (next.pos() != cur.pos() || next.par() != cur.par())
1995 if (cur.pos() == cur.par()->size()
1996 || !cur.par()->isInset(cur.pos()))
1998 Inset const * inset = cur.par()->getInset(cur.pos());
1999 if (inset->needFullRow() || inset->display())
2006 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2008 // Get the row first.
2010 RowList::iterator row = getRowNearY(y);
2012 pos_type const column = getColumnNearX(row, x, bound);
2013 cur.par(row->par());
2014 cur.pos(row->pos() + column);
2016 cur.y(y + row->baseline());
2019 if (beforeFullRowInset(*this, cur)) {
2020 pos_type last = lastPrintablePos(*this, row);
2021 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2023 cur.iy(y + row->height() + boost::next(row)->baseline());
2024 cur.irow(boost::next(row));
2030 cur.boundary(bound);
2034 void LyXText::cursorLeft(bool internal)
2036 if (cursor.pos() > 0) {
2037 bool boundary = cursor.boundary();
2038 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2039 if (!internal && !boundary &&
2040 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2041 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2042 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2043 ParagraphList::iterator pit = boost::prior(cursor.par());
2044 setCursor(pit, pit->size());
2049 void LyXText::cursorRight(bool internal)
2051 bool const at_end = (cursor.pos() == cursor.par()->size());
2052 bool const at_newline = !at_end &&
2053 cursor.par()->isNewline(cursor.pos());
2055 if (!internal && cursor.boundary() && !at_newline)
2056 setCursor(cursor.par(), cursor.pos(), true, false);
2058 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2060 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2061 setCursor(cursor.par(), cursor.pos(), true, true);
2062 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2063 setCursor(boost::next(cursor.par()), 0);
2067 void LyXText::cursorUp(bool selecting)
2070 int x = cursor.x_fix();
2071 int y = cursor.y() - cursor.row()->baseline() - 1;
2072 setCursorFromCoordinates(x, y);
2075 int y1 = cursor.iy() - topy;
2078 Inset * inset_hit = checkInsetHit(x, y1);
2079 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2080 inset_hit->localDispatch(
2081 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
2085 setCursorFromCoordinates(bv(), cursor.x_fix(),
2086 cursor.y() - cursor.row()->baseline() - 1);
2091 void LyXText::cursorDown(bool selecting)
2094 int x = cursor.x_fix();
2095 int y = cursor.y() - cursor.row()->baseline() +
2096 cursor.row()->height() + 1;
2097 setCursorFromCoordinates(x, y);
2098 if (!selecting && cursor.row() == cursor.irow()) {
2100 int y1 = cursor.iy() - topy;
2103 Inset * inset_hit = checkInsetHit(x, y1);
2104 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2105 FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
2106 inset_hit->localDispatch(cmd);
2110 setCursorFromCoordinates(bv(), cursor.x_fix(),
2111 cursor.y() - cursor.row()->baseline()
2112 + cursor.row()->height() + 1);
2117 void LyXText::cursorUpParagraph()
2119 if (cursor.pos() > 0) {
2120 setCursor(cursor.par(), 0);
2122 else if (cursor.par() != ownerParagraphs().begin()) {
2123 setCursor(boost::prior(cursor.par()), 0);
2128 void LyXText::cursorDownParagraph()
2130 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2131 setCursor(boost::next(cursor.par()), 0);
2133 setCursor(cursor.par(), cursor.par()->size());
2137 // fix the cursor `cur' after a characters has been deleted at `where'
2138 // position. Called by deleteEmptyParagraphMechanism
2139 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2140 LyXCursor const & where)
2142 // if cursor is not in the paragraph where the delete occured,
2144 if (cur.par() != where.par())
2147 // if cursor position is after the place where the delete occured,
2149 if (cur.pos() > where.pos())
2150 cur.pos(cur.pos()-1);
2152 // check also if we don't want to set the cursor on a spot behind the
2153 // pagragraph because we erased the last character.
2154 if (cur.pos() > cur.par()->size())
2155 cur.pos(cur.par()->size());
2157 // recompute row et al. for this cursor
2158 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2162 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2164 // Would be wrong to delete anything if we have a selection.
2165 if (selection.set())
2168 // We allow all kinds of "mumbo-jumbo" when freespacing.
2169 if (old_cursor.par()->layout()->free_spacing
2170 || old_cursor.par()->isFreeSpacing()) {
2174 /* Ok I'll put some comments here about what is missing.
2175 I have fixed BackSpace (and thus Delete) to not delete
2176 double-spaces automagically. I have also changed Cut,
2177 Copy and Paste to hopefully do some sensible things.
2178 There are still some small problems that can lead to
2179 double spaces stored in the document file or space at
2180 the beginning of paragraphs. This happens if you have
2181 the cursor betwenn to spaces and then save. Or if you
2182 cut and paste and the selection have a space at the
2183 beginning and then save right after the paste. I am
2184 sure none of these are very hard to fix, but I will
2185 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2186 that I can get some feedback. (Lgb)
2189 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2190 // delete the LineSeparator.
2193 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2194 // delete the LineSeparator.
2197 // If the pos around the old_cursor were spaces, delete one of them.
2198 if (old_cursor.par() != cursor.par()
2199 || old_cursor.pos() != cursor.pos()) {
2200 // Only if the cursor has really moved
2202 if (old_cursor.pos() > 0
2203 && old_cursor.pos() < old_cursor.par()->size()
2204 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2205 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2206 old_cursor.par()->erase(old_cursor.pos() - 1);
2207 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2209 #ifdef WITH_WARNINGS
2210 #warning This will not work anymore when we have multiple views of the same buffer
2211 // In this case, we will have to correct also the cursors held by
2212 // other bufferviews. It will probably be easier to do that in a more
2213 // automated way in LyXCursor code. (JMarc 26/09/2001)
2215 // correct all cursors held by the LyXText
2216 fixCursorAfterDelete(cursor, old_cursor);
2217 fixCursorAfterDelete(selection.cursor,
2219 fixCursorAfterDelete(selection.start,
2221 fixCursorAfterDelete(selection.end, old_cursor);
2222 fixCursorAfterDelete(last_sel_cursor,
2224 fixCursorAfterDelete(toggle_cursor, old_cursor);
2225 fixCursorAfterDelete(toggle_end_cursor,
2231 // don't delete anything if this is the ONLY paragraph!
2232 if (ownerParagraphs().size() == 1)
2235 // Do not delete empty paragraphs with keepempty set.
2236 if (old_cursor.par()->layout()->keepempty)
2239 // only do our magic if we changed paragraph
2240 if (old_cursor.par() == cursor.par())
2243 // record if we have deleted a paragraph
2244 // we can't possibly have deleted a paragraph before this point
2245 bool deleted = false;
2247 if (old_cursor.par()->empty() ||
2248 (old_cursor.par()->size() == 1 &&
2249 old_cursor.par()->isLineSeparator(0))) {
2250 // ok, we will delete anything
2251 LyXCursor tmpcursor;
2255 bool selection_position_was_oldcursor_position = (
2256 selection.cursor.par() == old_cursor.par()
2257 && selection.cursor.pos() == old_cursor.pos());
2259 if (old_cursor.row() != rows().begin()) {
2261 prevrow = boost::prior(old_cursor.row());
2262 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2264 cursor = old_cursor; // that undo can restore the right cursor position
2265 #warning FIXME. --end() iterator is usable here
2266 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2267 while (endpit != ownerParagraphs().end() &&
2268 endpit->getDepth()) {
2272 setUndo(bv(), Undo::DELETE, old_cursor.par(),
2273 boost::prior(endpit));
2277 removeRow(old_cursor.row());
2279 ownerParagraphs().erase(old_cursor.par());
2281 /* Breakagain the next par. Needed because of
2282 * the parindent that can occur or dissappear.
2283 * The next row can change its height, if
2284 * there is another layout before */
2285 if (boost::next(prevrow) != rows().end()) {
2286 breakAgain(boost::next(prevrow));
2289 setHeightOfRow(prevrow);
2291 RowList::iterator nextrow = boost::next(old_cursor.row());
2292 const_cast<LyXText *>(this)->postPaint(
2293 old_cursor.y() - old_cursor.row()->baseline());
2296 cursor = old_cursor; // that undo can restore the right cursor position
2297 #warning FIXME. --end() iterator is usable here
2298 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2299 while (endpit != ownerParagraphs().end() &&
2300 endpit->getDepth()) {
2304 setUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
2308 removeRow(old_cursor.row());
2310 ownerParagraphs().erase(old_cursor.par());
2312 /* Breakagain the next par. Needed because of
2313 the parindent that can occur or dissappear.
2314 The next row can change its height, if
2315 there is another layout before */
2316 if (nextrow != rows().end()) {
2317 breakAgain(nextrow);
2323 setCursorIntern(cursor.par(), cursor.pos());
2325 if (selection_position_was_oldcursor_position) {
2326 // correct selection
2327 selection.cursor = cursor;
2331 if (old_cursor.par()->stripLeadingSpaces()) {
2332 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2334 setCursorIntern(cursor.par(), cursor.pos());
2335 selection.cursor = cursor;
2342 ParagraphList & LyXText::ownerParagraphs() const
2345 return inset_owner->paragraphs;
2347 return bv_owner->buffer()->paragraphs;
2351 LyXText::refresh_status LyXText::refreshStatus() const
2353 return refresh_status_;
2357 void LyXText::clearPaint()
2359 refresh_status_ = REFRESH_NONE;
2360 refresh_row = rows().end();
2365 void LyXText::postPaint(int start_y)
2367 refresh_status old = refresh_status_;
2369 refresh_status_ = REFRESH_AREA;
2370 refresh_row = rows().end();
2372 if (old != REFRESH_NONE && refresh_y < start_y)
2375 refresh_y = start_y;
2380 // We are an inset's lyxtext. Tell the top-level lyxtext
2381 // it needs to update the row we're in.
2382 LyXText * t = bv()->text;
2383 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2387 // FIXME: we should probably remove this y parameter,
2388 // make refresh_y be 0, and use row->y etc.
2389 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2391 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2392 refresh_status_ = REFRESH_AREA;
2395 refresh_y = start_y;
2398 if (refresh_status_ == REFRESH_AREA)
2401 refresh_status_ = REFRESH_ROW;
2407 // We are an inset's lyxtext. Tell the top-level lyxtext
2408 // it needs to update the row we're in.
2409 LyXText * t = bv()->text;
2410 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2414 bool LyXText::isInInset() const
2416 // Sub-level has non-null bv owner and
2417 // non-null inset owner.
2418 return inset_owner != 0 && bv_owner != 0;
2422 int defaultRowHeight()
2424 LyXFont const font(LyXFont::ALL_SANE);
2425 return int(font_metrics::maxAscent(font)
2426 + font_metrics::maxDescent(font) * 1.5);