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 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
32 #include "lyxrow_funcs.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetfloat.h"
37 #include "support/LAssert.h"
38 #include "support/textutils.h"
39 #include "support/lstrings.h"
41 #include "support/BoostFormat.h"
51 LyXText::LyXText(BufferView * bv)
52 : height(0), width(0), anchor_row_offset_(0),
53 inset_owner(0), the_locking_inset(0), bv_owner(bv)
55 anchor_row_ = rows().end();
56 need_break_row = rows().end();
57 refresh_row = rows().end();
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
67 anchor_row_ = rows().end();
68 need_break_row = rows().end();
69 refresh_row = rows().end();
75 void LyXText::init(BufferView * bview, bool reinit)
79 need_break_row = rows().end();
81 copylayouttype.erase();
84 } else if (!rowlist_.empty())
87 ParagraphList::iterator pit = ownerParagraphs().begin();
88 ParagraphList::iterator end = ownerParagraphs().end();
90 current_font = getFont(bview->buffer(), pit, 0);
92 for (; pit != end; ++pit) {
93 insertParagraph(pit, rowlist_.end());
95 setCursorIntern(rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
106 ParagraphList & /*plist*/,
107 ParagraphList::iterator pit)
109 LyXTextClass const & tclass = buf->params.getLyXTextClass();
110 LyXFont tmpfont(font);
111 Paragraph::depth_type par_depth = pit->getDepth();
113 Paragraph * par = &*pit;
115 // Resolve against environment font information
116 while (par && par_depth && !tmpfont.resolved()) {
117 par = par->outerHook();
119 tmpfont.realize(par->layout()->font);
120 par_depth = par->getDepth();
124 tmpfont.realize(tclass.defaultfont());
132 // Gets the fully instantiated font at a given position in a paragraph
133 // Basically the same routine as Paragraph::getFont() in paragraph.C.
134 // The difference is that this one is used for displaying, and thus we
135 // are allowed to make cosmetic improvements. For instance make footnotes
137 // If position is -1, we get the layout font of the paragraph.
138 // If position is -2, we get the font of the manual label of the paragraph.
139 LyXFont const LyXText::getFont(Buffer const * buf, ParagraphList::iterator pit,
142 lyx::Assert(pos >= 0);
144 LyXLayout_ptr const & layout = pit->layout();
146 // We specialize the 95% common case:
147 if (!pit->getDepth()) {
148 if (layout->labeltype == LABEL_MANUAL
149 && pos < pit->beginningOfBody()) {
151 LyXFont f = pit->getFontSettings(buf->params, pos);
153 pit->inInset()->getDrawFont(f);
154 return f.realize(layout->reslabelfont);
156 LyXFont f = pit->getFontSettings(buf->params, pos);
158 pit->inInset()->getDrawFont(f);
159 return f.realize(layout->resfont);
163 // The uncommon case need not be optimized as much
167 if (pos < pit->beginningOfBody()) {
169 layoutfont = layout->labelfont;
172 layoutfont = layout->font;
175 LyXFont tmpfont = pit->getFontSettings(buf->params, pos);
176 tmpfont.realize(layoutfont);
179 pit->inInset()->getDrawFont(tmpfont);
181 return realizeFont(tmpfont, buf, ownerParagraphs(), pit);
185 LyXFont const LyXText::getLayoutFont(Buffer const * buf,
186 ParagraphList::iterator pit) const
188 LyXLayout_ptr const & layout = pit->layout();
190 if (!pit->getDepth()) {
191 return layout->resfont;
194 return realizeFont(layout->font, buf, ownerParagraphs(), pit);
198 LyXFont const LyXText::getLabelFont(Buffer const * buf,
199 ParagraphList::iterator pit) const
201 LyXLayout_ptr const & layout = pit->layout();
203 if (!pit->getDepth()) {
204 return layout->reslabelfont;
207 return realizeFont(layout->labelfont, buf, ownerParagraphs(), pit);
211 void LyXText::setCharFont(ParagraphList::iterator pit,
212 pos_type pos, LyXFont const & fnt,
215 Buffer const * buf = bv()->buffer();
216 LyXFont font = getFont(buf, pit, pos);
217 font.update(fnt, buf->params.language, toggleall);
218 // Let the insets convert their font
219 if (pit->isInset(pos)) {
220 Inset * inset = pit->getInset(pos);
221 if (isEditableInset(inset)) {
222 UpdatableInset * uinset =
223 static_cast<UpdatableInset *>(inset);
224 uinset->setFont(bv(), fnt, toggleall, true);
228 // Plug thru to version below:
229 setCharFont(buf, pit, pos, font);
233 void LyXText::setCharFont(Buffer const * buf, ParagraphList::iterator pit,
234 pos_type pos, LyXFont const & fnt)
238 LyXTextClass const & tclass = buf->params.getLyXTextClass();
239 LyXLayout_ptr const & layout = pit->layout();
241 // Get concrete layout font to reduce against
244 if (pos < pit->beginningOfBody())
245 layoutfont = layout->labelfont;
247 layoutfont = layout->font;
249 // Realize against environment font information
250 if (pit->getDepth()) {
251 #warning FIXME I think I hate this outerHood stuff.
252 Paragraph * tp = &*pit;
253 while (!layoutfont.resolved() && tp && tp->getDepth()) {
254 tp = tp->outerHook();
256 layoutfont.realize(tp->layout()->font);
260 layoutfont.realize(tclass.defaultfont());
262 // Now, reduce font against full layout font
263 font.reduce(layoutfont);
265 pit->setFont(pos, font);
269 // removes the row and reset the touched counters
270 void LyXText::removeRow(RowList::iterator rit)
272 /* FIXME: when we cache the bview, this should just
273 * become a postPaint(), I think */
274 if (refresh_row == rit) {
275 if (rit == rows().begin())
276 refresh_row = boost::next(rit);
278 refresh_row = boost::prior(rit);
280 // what about refresh_y
283 if (anchor_row_ == rit) {
284 if (rit != rows().begin()) {
285 anchor_row_ = boost::prior(rit);
286 anchor_row_offset_ += anchor_row_->height();
288 anchor_row_ = boost::next(rit);
289 anchor_row_offset_ -= rit->height();
293 // the text becomes smaller
294 height -= rit->height();
300 // remove all following rows of the paragraph of the specified row.
301 void LyXText::removeParagraph(RowList::iterator rit)
303 ParagraphList::iterator tmppit = rit->par();
306 while (rit != rows().end() && rit->par() == tmppit) {
307 RowList::iterator tmprit = boost::next(rit);
314 void LyXText::insertParagraph(ParagraphList::iterator pit,
315 RowList::iterator rowit)
317 // insert a new row, starting at position 0
319 RowList::iterator rit = rowlist_.insert(rowit, newrow);
321 // and now append the whole paragraph before the new row
322 appendParagraph(rit);
326 Inset * LyXText::getInset() const
328 ParagraphList::iterator pit = cursor.par();
329 pos_type const pos = cursor.pos();
331 if (pos < pit->size() && pit->isInset(pos)) {
332 return pit->getInset(pos);
338 void LyXText::toggleInset()
340 Inset * inset = getInset();
341 // is there an editable inset at cursor position?
342 if (!isEditableInset(inset)) {
343 // No, try to see if we are inside a collapsable inset
344 if (inset_owner && inset_owner->owner()
345 && inset_owner->owner()->isOpen()) {
346 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
347 inset_owner->owner()->close(bv());
348 bv()->getLyXText()->cursorRight(bv());
352 //bv()->owner()->message(inset->editMessage());
354 // do we want to keep this?? (JMarc)
355 if (!isHighlyEditableInset(inset))
356 setCursorParUndo(bv());
358 if (inset->isOpen()) {
364 bv()->updateInset(inset);
368 /* used in setlayout */
369 // Asger is not sure we want to do this...
370 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
373 LyXLayout_ptr const & layout = par.layout();
374 pos_type const psize = par.size();
377 for (pos_type pos = 0; pos < psize; ++pos) {
378 if (pos < par.beginningOfBody())
379 layoutfont = layout->labelfont;
381 layoutfont = layout->font;
383 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
384 tmpfont.reduce(layoutfont);
385 par.setFont(pos, tmpfont);
390 ParagraphList::iterator
391 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
392 LyXCursor & send_cur,
393 string const & layout)
395 ParagraphList::iterator endpit = boost::next(send_cur.par());
396 ParagraphList::iterator undoendpit = endpit;
397 ParagraphList::iterator pars_end = ownerParagraphs().end();
399 if (endpit != pars_end && endpit->getDepth()) {
400 while (endpit != pars_end && endpit->getDepth()) {
404 } else if (endpit != pars_end) {
405 // because of parindents etc.
409 setUndo(bv(), Undo::EDIT, &*sstart_cur.par(), &*undoendpit);
411 // ok we have a selection. This is always between sstart_cur
412 // and sel_end cursor
414 ParagraphList::iterator pit = sstart_cur.par();
415 ParagraphList::iterator epit = boost::next(send_cur.par());
417 LyXLayout_ptr const & lyxlayout =
418 bv()->buffer()->params.getLyXTextClass()[layout];
421 pit->applyLayout(lyxlayout);
422 makeFontEntriesLayoutSpecific(*bv()->buffer(), *pit);
423 ParagraphList::iterator fppit = pit;
424 fppit->params().spaceTop(lyxlayout->fill_top ?
425 VSpace(VSpace::VFILL)
426 : VSpace(VSpace::NONE));
427 fppit->params().spaceBottom(lyxlayout->fill_bottom ?
428 VSpace(VSpace::VFILL)
429 : VSpace(VSpace::NONE));
430 if (lyxlayout->margintype == MARGIN_MANUAL)
431 pit->setLabelWidthString(lyxlayout->labelstring());
434 } while (pit != epit);
440 // set layout over selection and make a total rebreak of those paragraphs
441 void LyXText::setLayout(string const & layout)
443 LyXCursor tmpcursor = cursor; /* store the current cursor */
445 // if there is no selection just set the layout
446 // of the current paragraph */
447 if (!selection.set()) {
448 selection.start = cursor; // dummy selection
449 selection.end = cursor;
451 ParagraphList::iterator endpit = setLayout(cursor, selection.start,
452 selection.end, layout);
453 redoParagraphs(selection.start, endpit);
455 // we have to reset the selection, because the
456 // geometry could have changed
457 setCursor(selection.start.par(),
458 selection.start.pos(), false);
459 selection.cursor = cursor;
460 setCursor(selection.end.par(), selection.end.pos(), false);
464 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
468 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
470 ParagraphList::iterator pit(cursor.par());
471 ParagraphList::iterator end(cursor.par());
472 ParagraphList::iterator start = pit;
474 if (selection.set()) {
475 pit = selection.start.par();
476 end = selection.end.par();
480 ParagraphList::iterator pastend = boost::next(end);
483 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
485 bool changed = false;
487 int prev_after_depth = 0;
488 #warning parlist ... could be nicer ?
489 if (start != ownerParagraphs().begin()) {
490 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
494 int const depth = pit->params().depth();
495 if (type == bv_funcs::INC_DEPTH) {
496 if (depth < prev_after_depth
497 && pit->layout()->labeltype != LABEL_BIBLIO) {
500 pit->params().depth(depth + 1);
507 pit->params().depth(depth - 1);
510 prev_after_depth = pit->getMaxDepthAfter();
522 // Wow, redoParagraphs is stupid.
524 setCursor(tmpcursor, &(*start), 0);
526 //redoParagraphs(tmpcursor, &(*pastend));
527 redoParagraphs(tmpcursor, &(*pastend));
529 // We need to actually move the text->cursor. I don't
530 // understand why ...
533 // we have to reset the visual selection because the
534 // geometry could have changed
535 if (selection.set()) {
536 setCursor(selection.start.par(), selection.start.pos());
537 selection.cursor = cursor;
538 setCursor(selection.end.par(), selection.end.pos());
541 // this handles the counter labels, and also fixes up
542 // depth values for follow-on (child) paragraphs
546 setCursor(tmpcursor.par(), tmpcursor.pos());
552 // set font over selection and make a total rebreak of those paragraphs
553 void LyXText::setFont(LyXFont const & font, bool toggleall)
555 // if there is no selection just set the current_font
556 if (!selection.set()) {
557 // Determine basis font
559 if (cursor.pos() < cursor.par()->beginningOfBody()) {
560 layoutfont = getLabelFont(bv()->buffer(),
563 layoutfont = getLayoutFont(bv()->buffer(),
566 // Update current font
567 real_current_font.update(font,
568 bv()->buffer()->params.language,
571 // Reduce to implicit settings
572 current_font = real_current_font;
573 current_font.reduce(layoutfont);
574 // And resolve it completely
575 real_current_font.realize(layoutfont);
580 LyXCursor tmpcursor = cursor; // store the current cursor
582 // ok we have a selection. This is always between sel_start_cursor
583 // and sel_end cursor
585 setUndo(bv(), Undo::EDIT,
586 &*selection.start.par(), &*boost::next(selection.end.par()));
588 cursor = selection.start;
589 while (cursor.par() != selection.end.par() ||
590 cursor.pos() < selection.end.pos())
592 if (cursor.pos() < cursor.par()->size()) {
593 // an open footnote should behave like a closed one
594 setCharFont(cursor.par(), cursor.pos(),
596 cursor.pos(cursor.pos() + 1);
599 cursor.par(boost::next(cursor.par()));
604 redoParagraphs(selection.start, boost::next(selection.end.par()));
606 // we have to reset the selection, because the
607 // geometry could have changed, but we keep
608 // it for user convenience
609 setCursor(selection.start.par(), selection.start.pos());
610 selection.cursor = cursor;
611 setCursor(selection.end.par(), selection.end.pos());
613 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
614 tmpcursor.boundary());
618 void LyXText::redoHeightOfParagraph()
620 RowList::iterator tmprow = cursor.row();
621 int y = cursor.y() - tmprow->baseline();
623 setHeightOfRow(tmprow);
625 while (tmprow != rows().begin()
626 && boost::prior(tmprow)->par() == tmprow->par()) {
628 y -= tmprow->height();
629 setHeightOfRow(tmprow);
634 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
638 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
640 RowList::iterator tmprow = cur.row();
642 int y = cur.y() - tmprow->baseline();
643 setHeightOfRow(tmprow);
645 while (tmprow != rows().begin()
646 && boost::prior(tmprow)->par() == tmprow->par()) {
648 y -= tmprow->height();
652 setCursor(cur.par(), cur.pos());
656 // deletes and inserts again all paragaphs between the cursor
657 // and the specified par
658 // This function is needed after SetLayout and SetFont etc.
659 void LyXText::redoParagraphs(LyXCursor const & cur,
660 ParagraphList::iterator endpit)
662 RowList::iterator tmprit = cur.row();
663 int y = cur.y() - tmprit->baseline();
665 ParagraphList::iterator first_phys_pit;
666 if (tmprit == rows().begin()) {
667 // A trick/hack for UNDO.
668 // This is needed because in an UNDO/REDO we could have
669 // changed the ownerParagrah() so the paragraph inside
670 // the row is NOT my really first par anymore.
671 // Got it Lars ;) (Jug 20011206)
672 first_phys_pit = ownerParagraphs().begin();
674 // In here prevrit could be set to rows().end(). (Lgb)
676 first_phys_pit = tmprit->par();
677 while (tmprit != rows().begin()
678 && boost::prior(tmprit)->par() == first_phys_pit)
681 y -= tmprit->height();
684 // Is it possible to put the prevrit setting in here? (Lgb)
687 RowList::iterator prevrit;
688 bool good_prevrit = false;
690 // It seems to mee that good_prevrit is not needed if we let
691 // a bad prevrit have the value rows().end() (Lgb)
692 if (tmprit != rows().begin()) {
693 prevrit = boost::prior(tmprit);
698 while (tmprit != rows().end() && tmprit->par() != endpit) {
699 RowList::iterator tmprit2 = tmprit++;
703 // Reinsert the paragraphs.
704 ParagraphList::iterator tmppit = first_phys_pit;
706 // See if this loop can be rewritten as a while loop instead.
707 // That should also make the code a bit easier to read. (Lgb)
709 if (tmppit != ownerParagraphs().end()) {
710 insertParagraph(tmppit, tmprit);
711 while (tmprit != rows().end()
712 && tmprit->par() == tmppit) {
717 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
720 // If the above changes are done, then we can compare prevrit
721 // with rows().end() here. (Lgb)
723 setHeightOfRow(prevrit);
724 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
726 setHeightOfRow(rows().begin());
727 const_cast<LyXText *>(this)->postPaint(0);
729 if (tmprit != rows().end())
730 setHeightOfRow(tmprit);
736 void LyXText::fullRebreak()
738 if (rows().empty()) {
743 RowList::iterator rows_end = rows().end();
745 if (need_break_row != rows_end) {
746 breakAgain(need_break_row);
747 need_break_row = rows_end;
753 // important for the screen
756 // the cursor set functions have a special mechanism. When they
757 // realize, that you left an empty paragraph, they will delete it.
758 // They also delete the corresponding row
760 // need the selection cursor:
761 void LyXText::setSelection()
763 bool const lsel = selection.set();
765 if (!selection.set()) {
766 last_sel_cursor = selection.cursor;
767 selection.start = selection.cursor;
768 selection.end = selection.cursor;
773 // first the toggling area
774 if (cursor.y() < last_sel_cursor.y()
775 || (cursor.y() == last_sel_cursor.y()
776 && cursor.x() < last_sel_cursor.x())) {
777 toggle_end_cursor = last_sel_cursor;
778 toggle_cursor = cursor;
780 toggle_end_cursor = cursor;
781 toggle_cursor = last_sel_cursor;
784 last_sel_cursor = cursor;
786 // and now the whole selection
788 if (selection.cursor.par() == cursor.par())
789 if (selection.cursor.pos() < cursor.pos()) {
790 selection.end = cursor;
791 selection.start = selection.cursor;
793 selection.end = selection.cursor;
794 selection.start = cursor;
796 else if (selection.cursor.y() < cursor.y() ||
797 (selection.cursor.y() == cursor.y()
798 && selection.cursor.x() < cursor.x())) {
799 selection.end = cursor;
800 selection.start = selection.cursor;
803 selection.end = selection.cursor;
804 selection.start = cursor;
807 // a selection with no contents is not a selection
808 if (selection.start.par() == selection.end.par() &&
809 selection.start.pos() == selection.end.pos())
810 selection.set(false);
812 if (inset_owner && (selection.set() || lsel))
813 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
817 string const LyXText::selectionAsString(Buffer const * buffer,
820 if (!selection.set()) return string();
822 // should be const ...
823 ParagraphList::iterator startpit = selection.start.par();
824 ParagraphList::iterator endpit = selection.end.par();
825 pos_type const startpos(selection.start.pos());
826 pos_type const endpos(selection.end.pos());
828 if (startpit == endpit) {
829 return startpit->asString(buffer, startpos, endpos, label);
834 // First paragraph in selection
835 result += startpit->asString(buffer, startpos, startpit->size(), label) + "\n\n";
837 // The paragraphs in between (if any)
838 ParagraphList::iterator pit = boost::next(startpit);
839 for (; pit != endpit; ++pit) {
840 result += pit->asString(buffer, 0, pit->size(), label) + "\n\n";
843 // Last paragraph in selection
844 result += endpit->asString(buffer, 0, endpos, label);
850 void LyXText::clearSelection()
852 selection.set(false);
853 selection.mark(false);
854 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
855 // reset this in the bv_owner!
856 if (bv_owner && bv_owner->text)
857 bv_owner->text->xsel_cache.set(false);
861 void LyXText::cursorHome()
863 setCursor(cursor.par(), cursor.row()->pos());
867 void LyXText::cursorEnd()
869 if (cursor.par()->empty())
872 RowList::iterator rit = cursor.row();
873 RowList::iterator next_rit = boost::next(rit);
874 ParagraphList::iterator pit = rit->par();
875 pos_type last_pos = lastPos(*this, rit);
877 if (next_rit == rows().end() || next_rit->par() != pit) {
881 (pit->getChar(last_pos) != ' ' && !pit->isNewline(last_pos))) {
886 setCursor(pit, last_pos);
890 void LyXText::cursorTop()
892 setCursor(ownerParagraphs().begin(), 0);
896 void LyXText::cursorBottom()
899 // This is how it should be:
900 // ParagraphList::iterator lastpit = boost::prior(ownerParagraphs().end());
901 ParagraphList::iterator lastpit = &ownerParagraphs().back();
902 int pos = lastpit->size();
903 setCursor(lastpit, pos);
907 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
909 // If the mask is completely neutral, tell user
910 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
911 // Could only happen with user style
912 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
916 // Try implicit word selection
917 // If there is a change in the language the implicit word selection
919 LyXCursor resetCursor = cursor;
920 bool implicitSelection = (font.language() == ignore_language
921 && font.number() == LyXFont::IGNORE)
922 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
925 setFont(font, toggleall);
927 // Implicit selections are cleared afterwards
928 //and cursor is set to the original position.
929 if (implicitSelection) {
931 cursor = resetCursor;
932 setCursor(cursor.par(), cursor.pos());
933 selection.cursor = cursor;
936 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
940 string LyXText::getStringToIndex()
942 // Try implicit word selection
943 // If there is a change in the language the implicit word selection
945 LyXCursor const reset_cursor = cursor;
946 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
949 if (!selection.set())
950 bv()->owner()->message(_("Nothing to index!"));
951 else if (selection.start.par() != selection.end.par())
952 bv()->owner()->message(_("Cannot index more than one paragraph!"));
954 idxstring = selectionAsString(bv()->buffer(), false);
956 // Reset cursors to their original position.
957 cursor = reset_cursor;
958 setCursor(cursor.par(), cursor.pos());
959 selection.cursor = cursor;
961 // Clear the implicit selection.
962 if (implicitSelection)
969 // the DTP switches for paragraphs. LyX will store them in the first
970 // physicla paragraph. When a paragraph is broken, the top settings rest,
971 // the bottom settings are given to the new one. So I can make shure,
972 // they do not duplicate themself and you cannnot make dirty things with
975 void LyXText::setParagraph(bool line_top, bool line_bottom,
976 bool pagebreak_top, bool pagebreak_bottom,
977 VSpace const & space_top,
978 VSpace const & space_bottom,
979 Spacing const & spacing,
981 string const & labelwidthstring,
984 LyXCursor tmpcursor = cursor;
985 if (!selection.set()) {
986 selection.start = cursor;
987 selection.end = cursor;
990 // make sure that the depth behind the selection are restored, too
991 ParagraphList::iterator endpit = boost::next(selection.end.par());
992 ParagraphList::iterator undoendpit = endpit;
993 ParagraphList::iterator pars_end = ownerParagraphs().end();
995 if (endpit != pars_end && endpit->getDepth()) {
996 while (endpit != pars_end && endpit->getDepth()) {
1000 } else if (endpit!= pars_end) {
1001 // because of parindents etc.
1005 setUndo(bv(), Undo::EDIT, &*selection.start.par(), &*undoendpit);
1008 ParagraphList::iterator tmppit = selection.end.par();
1010 while (tmppit != boost::prior(selection.start.par())) {
1011 setCursor(tmppit, 0);
1012 postPaint(cursor.y() - cursor.row()->baseline());
1014 ParagraphList::iterator pit = cursor.par();
1015 ParagraphParameters & params = pit->params();
1017 params.lineTop(line_top);
1018 params.lineBottom(line_bottom);
1019 params.pagebreakTop(pagebreak_top);
1020 params.pagebreakBottom(pagebreak_bottom);
1021 params.spaceTop(space_top);
1022 params.spaceBottom(space_bottom);
1023 params.spacing(spacing);
1024 // does the layout allow the new alignment?
1025 LyXLayout_ptr const & layout = pit->layout();
1027 if (align == LYX_ALIGN_LAYOUT)
1028 align = layout->align;
1029 if (align & layout->alignpossible) {
1030 if (align == layout->align)
1031 params.align(LYX_ALIGN_LAYOUT);
1033 params.align(align);
1035 pit->setLabelWidthString(labelwidthstring);
1036 params.noindent(noindent);
1037 tmppit = boost::prior(pit);
1040 redoParagraphs(selection.start, endpit);
1043 setCursor(selection.start.par(), selection.start.pos());
1044 selection.cursor = cursor;
1045 setCursor(selection.end.par(), selection.end.pos());
1047 setCursor(tmpcursor.par(), tmpcursor.pos());
1049 bv()->updateInset(inset_owner);
1053 // set the counter of a paragraph. This includes the labels
1054 void LyXText::setCounter(Buffer const * buf, ParagraphList::iterator pit)
1056 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1057 LyXLayout_ptr const & layout = pit->layout();
1059 if (pit != ownerParagraphs().begin()) {
1061 pit->params().appendix(boost::prior(pit)->params().appendix());
1062 if (!pit->params().appendix() &&
1063 pit->params().startOfAppendix()) {
1064 pit->params().appendix(true);
1065 textclass.counters().reset();
1067 pit->enumdepth = boost::prior(pit)->enumdepth;
1068 pit->itemdepth = boost::prior(pit)->itemdepth;
1070 pit->params().appendix(pit->params().startOfAppendix());
1075 /* Maybe we have to increment the enumeration depth.
1076 * BUT, enumeration in a footnote is considered in isolation from its
1077 * surrounding paragraph so don't increment if this is the
1078 * first line of the footnote
1079 * AND, bibliographies can't have their depth changed ie. they
1080 * are always of depth 0
1082 if (pit != ownerParagraphs().begin()
1083 && boost::prior(pit)->getDepth() < pit->getDepth()
1084 && boost::prior(pit)->layout()->labeltype == LABEL_COUNTER_ENUMI
1085 && pit->enumdepth < 3
1086 && layout->labeltype != LABEL_BIBLIO) {
1090 // Maybe we have to decrement the enumeration depth, see note above
1091 if (pit != ownerParagraphs().begin()
1092 && boost::prior(pit)->getDepth() > pit->getDepth()
1093 && layout->labeltype != LABEL_BIBLIO) {
1094 pit->enumdepth = pit->depthHook(pit->getDepth())->enumdepth;
1097 if (!pit->params().labelString().empty()) {
1098 pit->params().labelString(string());
1101 if (layout->margintype == MARGIN_MANUAL) {
1102 if (pit->params().labelWidthString().empty()) {
1103 pit->setLabelWidthString(layout->labelstring());
1106 pit->setLabelWidthString(string());
1109 // is it a layout that has an automatic label?
1110 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1111 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1115 if (i >= 0 && i <= buf->params.secnumdepth) {
1119 textclass.counters().step(layout->latexname());
1121 // Is there a label? Useful for Chapter layout
1122 if (!pit->params().appendix()) {
1123 s << layout->labelstring();
1125 s << layout->labelstring_appendix();
1128 // Use of an integer is here less than elegant. For now.
1129 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1130 if (!pit->params().appendix()) {
1131 numbertype = "sectioning";
1133 numbertype = "appendix";
1134 if (pit->isRightToLeftPar(buf->params))
1135 langtype = "hebrew";
1140 s << textclass.counters()
1141 .numberLabel(layout->latexname(),
1142 numbertype, langtype, head);
1144 pit->params().labelString(STRCONV(s.str()));
1146 // reset enum counters
1147 textclass.counters().reset("enum");
1148 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1149 textclass.counters().reset("enum");
1150 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1152 // Yes I know this is a really, really! bad solution
1154 string enumcounter("enum");
1156 switch (pit->enumdepth) {
1165 enumcounter += "iv";
1168 // not a valid enumdepth...
1172 textclass.counters().step(enumcounter);
1174 s << textclass.counters()
1175 .numberLabel(enumcounter, "enumeration");
1176 pit->params().labelString(STRCONV(s.str()));
1178 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1179 textclass.counters().step("bibitem");
1180 int number = textclass.counters().value("bibitem");
1181 if (pit->bibitem()) {
1182 pit->bibitem()->setCounter(number);
1183 pit->params().labelString(layout->labelstring());
1185 // In biblio should't be following counters but...
1187 string s = layout->labelstring();
1189 // the caption hack:
1190 if (layout->labeltype == LABEL_SENSITIVE) {
1191 ParagraphList::iterator tmppit = pit;
1194 while (tmppit != ownerParagraphs().end() &&
1196 // the single '=' is intended below
1197 && (in = tmppit->inInset()->owner())) {
1198 if (in->lyxCode() == Inset::FLOAT_CODE ||
1199 in->lyxCode() == Inset::WRAP_CODE) {
1203 tmppit = in->parOwner();
1209 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1211 textclass.counters().step(fl.type());
1213 // Doesn't work... yet.
1214 #if USE_BOOST_FORMAT
1215 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1216 // s << boost::format(_("%1$s %1$d:")
1218 // % buf->counters().value(fl.name());
1221 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1222 o << fl.name() << " #:";
1223 s = STRCONV(o.str());
1226 // par->SetLayout(0);
1227 // s = layout->labelstring;
1228 s = _("Senseless: ");
1231 pit->params().labelString(s);
1233 // reset the enumeration counter. They are always reset
1234 // when there is any other layout between
1235 // Just fall-through between the cases so that all
1236 // enum counters deeper than enumdepth is also reset.
1237 switch (pit->enumdepth) {
1239 textclass.counters().reset("enumi");
1241 textclass.counters().reset("enumii");
1243 textclass.counters().reset("enumiii");
1245 textclass.counters().reset("enumiv");
1251 // Updates all counters. Paragraphs with changed label string will be rebroken
1252 void LyXText::updateCounters()
1254 RowList::iterator rowit = rows().begin();
1255 ParagraphList::iterator pit = rowit->par();
1257 // CHECK if this is really needed. (Lgb)
1258 bv()->buffer()->params.getLyXTextClass().counters().reset();
1260 for (; pit != ownerParagraphs().end(); ++pit) {
1261 while (rowit->par() != pit)
1264 string const oldLabel = pit->params().labelString();
1267 if (pit != ownerParagraphs().begin())
1268 maxdepth = boost::prior(pit)->getMaxDepthAfter();
1270 if (pit->params().depth() > maxdepth)
1271 pit->params().depth(maxdepth);
1273 // setCounter can potentially change the labelString.
1274 setCounter(bv()->buffer(), pit);
1276 string const & newLabel = pit->params().labelString();
1278 if (oldLabel.empty() && !newLabel.empty()) {
1279 removeParagraph(rowit);
1280 appendParagraph(rowit);
1286 void LyXText::insertInset(Inset * inset)
1288 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1290 setUndo(bv(), Undo::FINISH, &*cursor.par(),
1291 &*boost::next(cursor.par()));
1293 cursor.par()->insertInset(cursor.pos(), inset);
1294 // Just to rebreak and refresh correctly.
1295 // The character will not be inserted a second time
1296 insertChar(Paragraph::META_INSET);
1297 // If we enter a highly editable inset the cursor should be to before
1298 // the inset. This couldn't happen before as Undo was not handled inside
1299 // inset now after the Undo LyX tries to call inset->Edit(...) again
1300 // and cannot do this as the cursor is behind the inset and GetInset
1301 // does not return the inset!
1302 if (isHighlyEditableInset(inset)) {
1309 void LyXText::copyEnvironmentType()
1311 copylayouttype = cursor.par()->layout()->name();
1315 void LyXText::pasteEnvironmentType()
1317 // do nothing if there has been no previous copyEnvironmentType()
1318 if (!copylayouttype.empty())
1319 setLayout(copylayouttype);
1323 void LyXText::cutSelection(bool doclear, bool realcut)
1325 // Stuff what we got on the clipboard. Even if there is no selection.
1327 // There is a problem with having the stuffing here in that the
1328 // larger the selection the slower LyX will get. This can be
1329 // solved by running the line below only when the selection has
1330 // finished. The solution used currently just works, to make it
1331 // faster we need to be more clever and probably also have more
1332 // calls to stuffClipboard. (Lgb)
1333 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1335 // This doesn't make sense, if there is no selection
1336 if (!selection.set())
1339 // OK, we have a selection. This is always between selection.start
1340 // and selection.end
1342 // make sure that the depth behind the selection are restored, too
1343 ParagraphList::iterator endpit = boost::next(selection.end.par());
1344 ParagraphList::iterator undoendpit = endpit;
1345 ParagraphList::iterator pars_end = ownerParagraphs().end();
1347 if (endpit != pars_end && endpit->getDepth()) {
1348 while (endpit != pars_end && endpit->getDepth()) {
1350 undoendpit = endpit;
1352 } else if (endpit != pars_end) {
1353 // because of parindents etc.
1357 setUndo(bv(), Undo::DELETE, &*selection.start.par(), &*undoendpit);
1359 // there are two cases: cut only within one paragraph or
1360 // more than one paragraph
1361 if (selection.start.par() == selection.end.par()) {
1362 // only within one paragraph
1363 endpit = selection.end.par();
1364 int pos = selection.end.pos();
1365 CutAndPaste::cutSelection(&*selection.start.par(), &*endpit,
1366 selection.start.pos(), pos,
1367 bv()->buffer()->params.textclass,
1369 selection.end.pos(pos);
1371 endpit = selection.end.par();
1372 int pos = selection.end.pos();
1373 CutAndPaste::cutSelection(&*selection.start.par(), &*endpit,
1374 selection.start.pos(), pos,
1375 bv()->buffer()->params.textclass,
1378 selection.end.par(endpit);
1379 selection.end.pos(pos);
1380 cursor.pos(selection.end.pos());
1384 // sometimes necessary
1386 selection.start.par()->stripLeadingSpaces();
1388 redoParagraphs(selection.start, endpit);
1390 // cutSelection can invalidate the cursor so we need to set
1392 // we prefer the end for when tracking changes
1393 cursor = selection.end;
1395 // need a valid cursor. (Lgb)
1398 setCursor(cursor.par(), cursor.pos());
1399 selection.cursor = cursor;
1404 void LyXText::copySelection()
1406 // stuff the selection onto the X clipboard, from an explicit copy request
1407 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1409 // this doesnt make sense, if there is no selection
1410 if (!selection.set())
1413 // ok we have a selection. This is always between selection.start
1414 // and sel_end cursor
1416 // copy behind a space if there is one
1417 while (selection.start.par()->size() > selection.start.pos()
1418 && selection.start.par()->isLineSeparator(selection.start.pos())
1419 && (selection.start.par() != selection.end.par()
1420 || selection.start.pos() < selection.end.pos()))
1421 selection.start.pos(selection.start.pos() + 1);
1423 CutAndPaste::copySelection(&*selection.start.par(),
1424 &*selection.end.par(),
1425 selection.start.pos(), selection.end.pos(),
1426 bv()->buffer()->params.textclass);
1430 void LyXText::pasteSelection()
1432 // this does not make sense, if there is nothing to paste
1433 if (!CutAndPaste::checkPastePossible())
1436 setUndo(bv(), Undo::INSERT,
1437 &*cursor.par(), &*boost::next(cursor.par()));
1440 ParagraphList::iterator actpit = cursor.par();
1441 int pos = cursor.pos();
1443 Paragraph * actpar = &*actpit;
1444 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1445 bv()->buffer()->params.textclass);
1447 redoParagraphs(cursor, endpar);
1449 setCursor(cursor.par(), cursor.pos());
1452 selection.cursor = cursor;
1453 setCursor(actpit, pos);
1459 void LyXText::setSelectionRange(lyx::pos_type length)
1464 selection.cursor = cursor;
1471 // simple replacing. The font of the first selected character is used
1472 void LyXText::replaceSelectionWithString(string const & str)
1474 setCursorParUndo(bv());
1477 if (!selection.set()) { // create a dummy selection
1478 selection.end = cursor;
1479 selection.start = cursor;
1482 // Get font setting before we cut
1483 pos_type pos = selection.end.pos();
1484 LyXFont const font = selection.start.par()
1485 ->getFontSettings(bv()->buffer()->params,
1486 selection.start.pos());
1488 // Insert the new string
1489 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1490 selection.end.par()->insertChar(pos, (*cit), font);
1494 // Cut the selection
1495 cutSelection(true, false);
1501 // needed to insert the selection
1502 void LyXText::insertStringAsLines(string const & str)
1504 ParagraphList::iterator pit = cursor.par();
1505 pos_type pos = cursor.pos();
1506 ParagraphList::iterator endpit = boost::next(cursor.par());
1508 setCursorParUndo(bv());
1510 // only to be sure, should not be neccessary
1513 Paragraph * par = &*pit;
1514 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1516 redoParagraphs(cursor, endpit);
1517 setCursor(cursor.par(), cursor.pos());
1518 selection.cursor = cursor;
1519 setCursor(pit, pos);
1524 // turns double-CR to single CR, others where converted into one
1525 // blank. Then InsertStringAsLines is called
1526 void LyXText::insertStringAsParagraphs(string const & str)
1528 string linestr(str);
1529 bool newline_inserted = false;
1530 for (string::size_type i = 0; i < linestr.length(); ++i) {
1531 if (linestr[i] == '\n') {
1532 if (newline_inserted) {
1533 // we know that \r will be ignored by
1534 // InsertStringA. Of course, it is a dirty
1535 // trick, but it works...
1536 linestr[i - 1] = '\r';
1540 newline_inserted = true;
1542 } else if (IsPrintable(linestr[i])) {
1543 newline_inserted = false;
1546 insertStringAsLines(linestr);
1550 void LyXText::checkParagraph(ParagraphList::iterator pit, pos_type pos)
1552 LyXCursor tmpcursor;
1556 RowList::iterator row = getRow(pit, pos, y);
1557 RowList::iterator beg = rows().begin();
1559 // is there a break one row above
1561 && boost::prior(row)->par() == row->par()) {
1562 z = rowBreakPoint(*boost::prior(row));
1563 if (z >= row->pos()) {
1564 // set the dimensions of the row above
1565 y -= boost::prior(row)->height();
1568 breakAgain(boost::prior(row));
1570 // set the cursor again. Otherwise
1571 // dangling pointers are possible
1572 setCursor(cursor.par(), cursor.pos(),
1573 false, cursor.boundary());
1574 selection.cursor = cursor;
1579 int const tmpheight = row->height();
1580 pos_type const tmplast = lastPos(*this, row);
1583 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1584 postRowPaint(row, y);
1589 // check the special right address boxes
1590 if (pit->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1597 redoDrawingOfParagraph(tmpcursor);
1600 // set the cursor again. Otherwise dangling pointers are possible
1601 // also set the selection
1603 if (selection.set()) {
1605 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1606 false, selection.cursor.boundary());
1607 selection.cursor = cursor;
1608 setCursorIntern(selection.start.par(),
1609 selection.start.pos(),
1610 false, selection.start.boundary());
1611 selection.start = cursor;
1612 setCursorIntern(selection.end.par(),
1613 selection.end.pos(),
1614 false, selection.end.boundary());
1615 selection.end = cursor;
1616 setCursorIntern(last_sel_cursor.par(),
1617 last_sel_cursor.pos(),
1618 false, last_sel_cursor.boundary());
1619 last_sel_cursor = cursor;
1622 setCursorIntern(cursor.par(), cursor.pos(),
1623 false, cursor.boundary());
1627 // returns false if inset wasn't found
1628 bool LyXText::updateInset(Inset * inset)
1630 // first check the current paragraph
1631 int pos = cursor.par()->getPositionOfInset(inset);
1633 checkParagraph(cursor.par(), pos);
1637 // check every paragraph
1639 ParagraphList::iterator par = ownerParagraphs().begin();
1640 ParagraphList::iterator end = ownerParagraphs().end();
1641 for (; par != end; ++par) {
1642 pos = par->getPositionOfInset(inset);
1644 checkParagraph(par, pos);
1653 bool LyXText::setCursor(ParagraphList::iterator pit,
1655 bool setfont, bool boundary)
1657 LyXCursor old_cursor = cursor;
1658 setCursorIntern(pit, pos, setfont, boundary);
1659 return deleteEmptyParagraphMechanism(old_cursor);
1663 void LyXText::setCursor(LyXCursor & cur, ParagraphList::iterator pit,
1664 pos_type pos, bool boundary)
1666 lyx::Assert(pit != ownerParagraphs().end());
1670 cur.boundary(boundary);
1672 // get the cursor y position in text
1674 RowList::iterator row = getRow(pit, pos, y);
1675 RowList::iterator beg = rows().begin();
1677 RowList::iterator old_row = row;
1679 // if we are before the first char of this row and are still in the
1680 // same paragraph and there is a previous row then put the cursor on
1681 // the end of the previous row
1682 cur.iy(y + row->baseline());
1684 if (row != beg && pos &&
1685 boost::prior(row)->par() == row->par() &&
1686 pos < pit->size() &&
1687 pit->getChar(pos) == Paragraph::META_INSET &&
1688 (ins = pit->getInset(pos)) && (ins->needFullRow() || ins->display()))
1695 // y is now the beginning of the cursor row
1696 y += row->baseline();
1697 // y is now the cursor baseline
1700 pos_type last = lastPrintablePos(*this, old_row);
1702 // None of these should happen, but we're scaredy-cats
1703 if (pos > pit->size()) {
1704 lyxerr << "dont like 1 please report" << endl;
1707 } else if (pos > last + 1) {
1708 lyxerr << "dont like 2 please report" << endl;
1709 // This shouldn't happen.
1712 } else if (pos < row->pos()) {
1713 lyxerr << "dont like 3 please report" << endl;
1718 // now get the cursors x position
1719 float x = getCursorX(row, pos, last, boundary);
1722 if (old_row != row) {
1723 x = getCursorX(old_row, pos, last, boundary);
1727 /* We take out this for the time being because 1) the redraw code is not
1728 prepared to this yet and 2) because some good policy has yet to be decided
1729 while editting: for instance how to act on rows being created/deleted
1733 //if the cursor is in a visible row, anchor to it
1735 if (topy < y && y < topy + bv()->workHeight())
1741 float LyXText::getCursorX(RowList::iterator rit,
1742 pos_type pos, pos_type last, bool boundary) const
1744 pos_type cursor_vpos = 0;
1746 float fill_separator;
1748 float fill_label_hfill;
1749 // This call HAS to be here because of the BidiTables!!!
1750 prepareToPrint(rit, x, fill_separator, fill_hfill,
1753 ParagraphList::iterator rit_par = rit->par();
1754 pos_type const rit_pos = rit->pos();
1757 cursor_vpos = rit_pos;
1758 else if (pos > last && !boundary)
1759 cursor_vpos = (rit_par->isRightToLeftPar(bv()->buffer()->params))
1760 ? rit_pos : last + 1;
1761 else if (pos > rit_pos && (pos > last || boundary))
1762 /// Place cursor after char at (logical) position pos - 1
1763 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1764 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1766 /// Place cursor before char at (logical) position pos
1767 cursor_vpos = (bidi_level(pos) % 2 == 0)
1768 ? log2vis(pos) : log2vis(pos) + 1;
1770 pos_type body_pos = rit_par->beginningOfBody();
1771 if ((body_pos > 0) &&
1772 ((body_pos - 1 > last) || !rit_par->isLineSeparator(body_pos - 1)))
1775 for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1776 pos_type pos = vis2log(vpos);
1777 if (body_pos > 0 && pos == body_pos - 1) {
1778 x += fill_label_hfill +
1779 font_metrics::width(
1780 rit_par->layout()->labelsep,
1781 getLabelFont(bv()->buffer(), rit_par));
1782 if (rit_par->isLineSeparator(body_pos - 1))
1783 x -= singleWidth(rit_par, body_pos - 1);
1786 if (hfillExpansion(*this, rit, pos)) {
1787 x += singleWidth(rit_par, pos);
1788 if (pos >= body_pos)
1791 x += fill_label_hfill;
1792 } else if (rit_par->isSeparator(pos)) {
1793 x += singleWidth(rit_par, pos);
1794 if (pos >= body_pos)
1795 x += fill_separator;
1797 x += singleWidth(rit_par, pos);
1803 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1804 pos_type pos, bool setfont, bool boundary)
1806 InsetText * it = static_cast<InsetText *>(pit->inInset());
1808 if (it != inset_owner) {
1809 lyxerr[Debug::INSETS] << "InsetText is " << it
1811 << "inset_owner is "
1812 << inset_owner << endl;
1813 #ifdef WITH_WARNINGS
1814 #warning I believe this code is wrong. (Lgb)
1815 #warning Jürgen, have a look at this. (Lgb)
1816 #warning Hmmm, I guess you are right but we
1817 #warning should verify when this is needed
1819 // Jürgen, would you like to have a look?
1820 // I guess we need to move the outer cursor
1821 // and open and lock the inset (bla bla bla)
1822 // stuff I don't know... so can you have a look?
1824 // I moved the lyxerr stuff in here so we can see if
1825 // this is actually really needed and where!
1827 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1832 setCursor(cursor, pit, pos, boundary);
1838 void LyXText::setCurrentFont()
1840 pos_type pos = cursor.pos();
1841 ParagraphList::iterator pit = cursor.par();
1843 if (cursor.boundary() && pos > 0)
1847 if (pos == pit->size())
1849 else // potentional bug... BUG (Lgb)
1850 if (pit->isSeparator(pos)) {
1851 if (pos > cursor.row()->pos() &&
1852 bidi_level(pos) % 2 ==
1853 bidi_level(pos - 1) % 2)
1855 else if (pos + 1 < pit->size())
1861 pit->getFontSettings(bv()->buffer()->params, pos);
1862 real_current_font = getFont(bv()->buffer(), pit, pos);
1864 if (cursor.pos() == pit->size() &&
1865 isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1866 !cursor.boundary()) {
1867 Language const * lang =
1868 pit->getParLanguage(bv()->buffer()->params);
1869 current_font.setLanguage(lang);
1870 current_font.setNumber(LyXFont::OFF);
1871 real_current_font.setLanguage(lang);
1872 real_current_font.setNumber(LyXFont::OFF);
1877 // returns the column near the specified x-coordinate of the row
1878 // x is set to the real beginning of this column
1880 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1883 float fill_separator;
1885 float fill_label_hfill;
1887 prepareToPrint(rit, tmpx, fill_separator,
1888 fill_hfill, fill_label_hfill);
1890 pos_type vc = rit->pos();
1891 pos_type last = lastPrintablePos(*this, rit);
1894 ParagraphList::iterator rit_par = rit->par();
1895 LyXLayout_ptr const & layout = rit->par()->layout();
1897 bool left_side = false;
1899 pos_type body_pos = rit_par->beginningOfBody();
1900 float last_tmpx = tmpx;
1903 (body_pos - 1 > last ||
1904 !rit_par->isLineSeparator(body_pos - 1)))
1907 // check for empty row
1908 if (!rit_par->size()) {
1913 while (vc <= last && tmpx <= x) {
1916 if (body_pos > 0 && c == body_pos - 1) {
1917 tmpx += fill_label_hfill +
1918 font_metrics::width(layout->labelsep,
1919 getLabelFont(bv()->buffer(), &*rit_par));
1920 if (rit_par->isLineSeparator(body_pos - 1))
1921 tmpx -= singleWidth(rit_par, body_pos - 1);
1924 if (hfillExpansion(*this, rit, c)) {
1925 tmpx += singleWidth(rit_par, c);
1929 tmpx += fill_label_hfill;
1930 } else if (rit_par->isSeparator(c)) {
1931 tmpx += singleWidth(rit_par, c);
1933 tmpx+= fill_separator;
1935 tmpx += singleWidth(rit_par, c);
1940 if ((tmpx + last_tmpx) / 2 > x) {
1945 if (vc > last + 1) // This shouldn't happen.
1949 // This (rtl_support test) is not needed, but gives
1950 // some speedup if rtl_support=false
1951 bool const lastrow = lyxrc.rtl_support &&
1952 (boost::next(rit) == rowlist_.end() ||
1953 boost::next(rit)->par() != rit_par);
1954 // If lastrow is false, we don't need to compute
1955 // the value of rtl.
1956 bool const rtl = (lastrow)
1957 ? rit_par->isRightToLeftPar(bv()->buffer()->params)
1960 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1961 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1963 else if (vc == rit->pos()) {
1965 if (bidi_level(c) % 2 == 1)
1968 c = vis2log(vc - 1);
1969 bool const rtl = (bidi_level(c) % 2 == 1);
1970 if (left_side == rtl) {
1972 boundary = isBoundary(bv()->buffer(), *rit_par, c);
1976 if (rit->pos() <= last && c > last
1977 && rit_par->isNewline(last)) {
1978 if (bidi_level(last) % 2 == 0)
1979 tmpx -= singleWidth(rit_par, last);
1981 tmpx += singleWidth(rit_par, last);
1991 void LyXText::setCursorFromCoordinates(int x, int y)
1993 LyXCursor old_cursor = cursor;
1995 setCursorFromCoordinates(cursor, x, y);
1997 deleteEmptyParagraphMechanism(old_cursor);
2004 * return true if the cursor given is at the end of a row,
2005 * and the next row is filled by an inset that spans an entire
2008 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
2010 if (boost::next(row) == lt.rows().end())
2012 Row const & next = *boost::next(row);
2014 if (next.pos() != cur.pos() || next.par() != cur.par())
2016 if (!cur.par()->isInset(cur.pos()))
2018 Inset const * inset = cur.par()->getInset(cur.pos());
2019 if (inset->needFullRow() || inset->display())
2026 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2028 // Get the row first.
2030 RowList::iterator row = getRowNearY(y);
2032 pos_type const column = getColumnNearX(row, x, bound);
2033 cur.par(row->par());
2034 cur.pos(row->pos() + column);
2036 cur.y(y + row->baseline());
2039 if (beforeFullRowInset(*this, row, cur)) {
2040 pos_type last = lastPrintablePos(*this, row);
2041 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2043 cur.iy(y + row->height() + boost::next(row)->baseline());
2044 cur.irow(boost::next(row));
2050 cur.boundary(bound);
2054 void LyXText::cursorLeft(bool internal)
2056 if (cursor.pos() > 0) {
2057 bool boundary = cursor.boundary();
2058 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2059 if (!internal && !boundary &&
2060 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
2061 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2062 } else if (cursor.par() != ownerParagraphs().begin()) { // steps into the above paragraph.
2063 ParagraphList::iterator pit = boost::prior(cursor.par());
2064 setCursor(pit, pit->size());
2069 void LyXText::cursorRight(bool internal)
2071 bool const at_end = (cursor.pos() == cursor.par()->size());
2072 bool const at_newline = !at_end &&
2073 cursor.par()->isNewline(cursor.pos());
2075 if (!internal && cursor.boundary() && !at_newline)
2076 setCursor(cursor.par(), cursor.pos(), true, false);
2078 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2080 isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
2081 setCursor(cursor.par(), cursor.pos(), true, true);
2082 } else if (boost::next(cursor.par()) != ownerParagraphs().end())
2083 setCursor(boost::next(cursor.par()), 0);
2087 void LyXText::cursorUp(bool selecting)
2090 int x = cursor.x_fix();
2091 int y = cursor.y() - cursor.row()->baseline() - 1;
2092 setCursorFromCoordinates(x, y);
2095 int y1 = cursor.iy() - topy;
2098 Inset * inset_hit = checkInsetHit(x, y1);
2099 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2100 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2104 setCursorFromCoordinates(bv(), cursor.x_fix(),
2105 cursor.y() - cursor.row()->baseline() - 1);
2110 void LyXText::cursorDown(bool selecting)
2113 int x = cursor.x_fix();
2114 int y = cursor.y() - cursor.row()->baseline() +
2115 cursor.row()->height() + 1;
2116 setCursorFromCoordinates(x, y);
2117 if (!selecting && cursor.row() == cursor.irow()) {
2119 int y1 = cursor.iy() - topy;
2122 Inset * inset_hit = checkInsetHit(x, y1);
2123 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2124 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2128 setCursorFromCoordinates(bv(), cursor.x_fix(),
2129 cursor.y() - cursor.row()->baseline()
2130 + cursor.row()->height() + 1);
2135 void LyXText::cursorUpParagraph()
2137 if (cursor.pos() > 0) {
2138 setCursor(cursor.par(), 0);
2140 else if (cursor.par() != ownerParagraphs().begin()) {
2141 setCursor(boost::prior(cursor.par()), 0);
2146 void LyXText::cursorDownParagraph()
2148 if (boost::next(cursor.par()) != ownerParagraphs().end()) {
2149 setCursor(boost::next(cursor.par()), 0);
2151 setCursor(cursor.par(), cursor.par()->size());
2155 // fix the cursor `cur' after a characters has been deleted at `where'
2156 // position. Called by deleteEmptyParagraphMechanism
2157 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2158 LyXCursor const & where)
2160 // if cursor is not in the paragraph where the delete occured,
2162 if (cur.par() != where.par())
2165 // if cursor position is after the place where the delete occured,
2167 if (cur.pos() > where.pos())
2168 cur.pos(cur.pos()-1);
2170 // check also if we don't want to set the cursor on a spot behind the
2171 // pagragraph because we erased the last character.
2172 if (cur.pos() > cur.par()->size())
2173 cur.pos(cur.par()->size());
2175 // recompute row et al. for this cursor
2176 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2180 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2182 // Would be wrong to delete anything if we have a selection.
2183 if (selection.set())
2186 // We allow all kinds of "mumbo-jumbo" when freespacing.
2187 if (old_cursor.par()->layout()->free_spacing
2188 || old_cursor.par()->isFreeSpacing()) {
2192 /* Ok I'll put some comments here about what is missing.
2193 I have fixed BackSpace (and thus Delete) to not delete
2194 double-spaces automagically. I have also changed Cut,
2195 Copy and Paste to hopefully do some sensible things.
2196 There are still some small problems that can lead to
2197 double spaces stored in the document file or space at
2198 the beginning of paragraphs. This happens if you have
2199 the cursor betwenn to spaces and then save. Or if you
2200 cut and paste and the selection have a space at the
2201 beginning and then save right after the paste. I am
2202 sure none of these are very hard to fix, but I will
2203 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2204 that I can get some feedback. (Lgb)
2207 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2208 // delete the LineSeparator.
2211 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2212 // delete the LineSeparator.
2215 // If the pos around the old_cursor were spaces, delete one of them.
2216 if (old_cursor.par() != cursor.par()
2217 || old_cursor.pos() != cursor.pos()) {
2218 // Only if the cursor has really moved
2220 if (old_cursor.pos() > 0
2221 && old_cursor.pos() < old_cursor.par()->size()
2222 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2223 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2224 old_cursor.par()->erase(old_cursor.pos() - 1);
2225 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2227 #ifdef WITH_WARNINGS
2228 #warning This will not work anymore when we have multiple views of the same buffer
2229 // In this case, we will have to correct also the cursors held by
2230 // other bufferviews. It will probably be easier to do that in a more
2231 // automated way in LyXCursor code. (JMarc 26/09/2001)
2233 // correct all cursors held by the LyXText
2234 fixCursorAfterDelete(cursor, old_cursor);
2235 fixCursorAfterDelete(selection.cursor,
2237 fixCursorAfterDelete(selection.start,
2239 fixCursorAfterDelete(selection.end, old_cursor);
2240 fixCursorAfterDelete(last_sel_cursor,
2242 fixCursorAfterDelete(toggle_cursor, old_cursor);
2243 fixCursorAfterDelete(toggle_end_cursor,
2249 // don't delete anything if this is the ONLY paragraph!
2250 if (ownerParagraphs().size() == 1)
2253 // Do not delete empty paragraphs with keepempty set.
2254 if (old_cursor.par()->layout()->keepempty)
2257 // only do our magic if we changed paragraph
2258 if (old_cursor.par() == cursor.par())
2261 // record if we have deleted a paragraph
2262 // we can't possibly have deleted a paragraph before this point
2263 bool deleted = false;
2265 if (old_cursor.par()->empty() ||
2266 (old_cursor.par()->size() == 1 &&
2267 old_cursor.par()->isLineSeparator(0))) {
2268 // ok, we will delete anything
2269 LyXCursor tmpcursor;
2273 if (old_cursor.row() != rows().begin()) {
2275 prevrow = boost::prior(old_cursor.row());
2276 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline() - prevrow->height());
2278 cursor = old_cursor; // that undo can restore the right cursor position
2279 #warning FIXME. --end() iterator is usable here
2280 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2281 while (endpit != ownerParagraphs().end() &&
2282 endpit->getDepth()) {
2286 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), &*endpit);
2290 removeRow(old_cursor.row());
2291 if (ownerParagraphs().begin() == old_cursor.par()) {
2292 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2294 #warning FIXME Do the proper ParagraphList operation here (Lgb)
2296 delete &*old_cursor.par();
2298 /* Breakagain the next par. Needed because of
2299 * the parindent that can occur or dissappear.
2300 * The next row can change its height, if
2301 * there is another layout before */
2302 if (boost::next(prevrow) != rows().end()) {
2303 breakAgain(boost::next(prevrow));
2306 setHeightOfRow(prevrow);
2308 RowList::iterator nextrow = boost::next(old_cursor.row());
2309 const_cast<LyXText *>(this)->postPaint(
2310 old_cursor.y() - old_cursor.row()->baseline());
2313 cursor = old_cursor; // that undo can restore the right cursor position
2314 #warning FIXME. --end() iterator is usable here
2315 ParagraphList::iterator endpit = boost::next(old_cursor.par());
2316 while (endpit != ownerParagraphs().end() &&
2317 endpit->getDepth()) {
2321 setUndo(bv(), Undo::DELETE, &*old_cursor.par(), &*endpit);
2325 removeRow(old_cursor.row());
2327 if (ownerParagraphs().begin() == old_cursor.par()) {
2328 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2330 #warning FIXME Do the proper ParagraphList operations here. (Lgb)
2331 delete &*old_cursor.par();
2333 /* Breakagain the next par. Needed because of
2334 the parindent that can occur or dissappear.
2335 The next row can change its height, if
2336 there is another layout before */
2337 if (nextrow != rows().end()) {
2338 breakAgain(nextrow);
2344 setCursorIntern(cursor.par(), cursor.pos());
2346 if (selection.cursor.par() == old_cursor.par()
2347 && selection.cursor.pos() == old_cursor.pos()) {
2348 // correct selection
2349 selection.cursor = cursor;
2353 if (old_cursor.par()->stripLeadingSpaces()) {
2354 redoParagraphs(old_cursor, boost::next(old_cursor.par()));
2356 setCursorIntern(cursor.par(), cursor.pos());
2357 selection.cursor = cursor;
2364 ParagraphList & LyXText::ownerParagraphs() const
2367 return inset_owner->paragraphs;
2369 return bv_owner->buffer()->paragraphs;
2373 void LyXText::ownerParagraph(Paragraph * p) const
2376 inset_owner->paragraph(p);
2378 bv_owner->buffer()->paragraphs.set(p);
2383 void LyXText::ownerParagraph(int id, Paragraph * p) const
2385 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2386 if (op && op->inInset()) {
2387 static_cast<InsetText *>(op->inInset())->paragraph(p);
2394 LyXText::refresh_status LyXText::refreshStatus() const
2396 return refresh_status_;
2400 void LyXText::clearPaint()
2402 refresh_status_ = REFRESH_NONE;
2403 refresh_row = rows().end();
2408 void LyXText::postPaint(int start_y)
2410 refresh_status old = refresh_status_;
2412 refresh_status_ = REFRESH_AREA;
2413 refresh_row = rows().end();
2415 if (old != REFRESH_NONE && refresh_y < start_y)
2418 refresh_y = start_y;
2423 // We are an inset's lyxtext. Tell the top-level lyxtext
2424 // it needs to update the row we're in.
2425 LyXText * t = bv()->text;
2426 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2430 // FIXME: we should probably remove this y parameter,
2431 // make refresh_y be 0, and use row->y etc.
2432 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2434 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2435 refresh_status_ = REFRESH_AREA;
2438 refresh_y = start_y;
2441 if (refresh_status_ == REFRESH_AREA)
2444 refresh_status_ = REFRESH_ROW;
2450 // We are an inset's lyxtext. Tell the top-level lyxtext
2451 // it needs to update the row we're in.
2452 LyXText * t = bv()->text;
2453 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2457 bool LyXText::isInInset() const
2459 // Sub-level has non-null bv owner and
2460 // non-null inset owner.
2461 return inset_owner != 0 && bv_owner != 0;
2465 int defaultRowHeight()
2467 LyXFont const font(LyXFont::ALL_SANE);
2468 return int(font_metrics::maxAscent(font)
2469 + font_metrics::maxDescent(font) * 1.5);