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"
33 #include "insets/inseterror.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetspecialchar.h"
36 #include "insets/insettext.h"
37 #include "insets/insetfloat.h"
38 #include "insets/insetwrap.h"
40 #include "support/LAssert.h"
41 #include "support/textutils.h"
42 #include "support/lstrings.h"
44 #include "support/BoostFormat.h"
54 LyXText::LyXText(BufferView * bv)
55 : height(0), width(0), anchor_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), bv_owner(bv)
58 anchor_row_ = rows().end();
59 need_break_row = rows().end();
60 refresh_row = rows().end();
65 LyXText::LyXText(BufferView * bv, InsetText * inset)
66 : height(0), width(0), anchor_row_offset_(0),
67 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
69 anchor_row_ = rows().end();
70 need_break_row = rows().end();
71 refresh_row = rows().end();
76 void LyXText::init(BufferView * bview, bool reinit)
80 need_break_row = rows().end();
82 copylayouttype.erase();
85 } else if (!rowlist_.empty())
88 Paragraph * par = ownerParagraph();
89 current_font = getFont(bview->buffer(), par, 0);
92 insertParagraph(par, rowlist_.end());
95 setCursorIntern(rowlist_.begin()->par(), 0);
96 selection.cursor = cursor;
104 LyXFont const realizeFont(LyXFont const & font,
108 LyXTextClass const & tclass = buf->params.getLyXTextClass();
109 LyXFont tmpfont(font);
110 Paragraph::depth_type par_depth = par->getDepth();
112 // Resolve against environment font information
113 while (par && par_depth && !tmpfont.resolved()) {
114 par = par->outerHook();
116 tmpfont.realize(par->layout()->font);
117 par_depth = par->getDepth();
121 tmpfont.realize(tclass.defaultfont());
129 // Gets the fully instantiated font at a given position in a paragraph
130 // Basically the same routine as Paragraph::getFont() in paragraph.C.
131 // The difference is that this one is used for displaying, and thus we
132 // are allowed to make cosmetic improvements. For instance make footnotes
134 // If position is -1, we get the layout font of the paragraph.
135 // If position is -2, we get the font of the manual label of the paragraph.
136 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
139 lyx::Assert(pos >= 0);
141 LyXLayout_ptr const & layout = par->layout();
143 // We specialize the 95% common case:
144 if (!par->getDepth()) {
145 if (layout->labeltype == LABEL_MANUAL
146 && pos < par->beginningOfBody()) {
148 LyXFont f = par->getFontSettings(buf->params, pos);
150 par->inInset()->getDrawFont(f);
151 return f.realize(layout->reslabelfont);
153 LyXFont f = par->getFontSettings(buf->params, pos);
155 par->inInset()->getDrawFont(f);
156 return f.realize(layout->resfont);
160 // The uncommon case need not be optimized as much
164 if (pos < par->beginningOfBody()) {
166 layoutfont = layout->labelfont;
169 layoutfont = layout->font;
172 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
173 tmpfont.realize(layoutfont);
176 par->inInset()->getDrawFont(tmpfont);
178 return realizeFont(tmpfont, buf, par);
182 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
184 LyXLayout_ptr const & layout = par->layout();
186 if (!par->getDepth()) {
187 return layout->resfont;
190 return realizeFont(layout->font, buf, par);
194 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
196 LyXLayout_ptr const & layout = par->layout();
198 if (!par->getDepth()) {
199 return layout->reslabelfont;
202 return realizeFont(layout->labelfont, buf, par);
206 void LyXText::setCharFont(Paragraph * par,
207 pos_type pos, LyXFont const & fnt,
210 Buffer const * buf = bv()->buffer();
211 LyXFont font = getFont(buf, par, pos);
212 font.update(fnt, buf->params.language, toggleall);
213 // Let the insets convert their font
214 if (par->isInset(pos)) {
215 Inset * inset = par->getInset(pos);
216 if (isEditableInset(inset)) {
217 UpdatableInset * uinset =
218 static_cast<UpdatableInset *>(inset);
219 uinset->setFont(bv(), fnt, toggleall, true);
223 // Plug thru to version below:
224 setCharFont(buf, par, pos, font);
228 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
229 pos_type pos, LyXFont const & fnt)
233 LyXTextClass const & tclass = buf->params.getLyXTextClass();
234 LyXLayout_ptr const & layout = par->layout();
236 // Get concrete layout font to reduce against
239 if (pos < par->beginningOfBody())
240 layoutfont = layout->labelfont;
242 layoutfont = layout->font;
244 // Realize against environment font information
245 if (par->getDepth()) {
246 Paragraph * tp = par;
247 while (!layoutfont.resolved() && tp && tp->getDepth()) {
248 tp = tp->outerHook();
250 layoutfont.realize(tp->layout()->font);
254 layoutfont.realize(tclass.defaultfont());
256 // Now, reduce font against full layout font
257 font.reduce(layoutfont);
259 par->setFont(pos, font);
263 // removes the row and reset the touched counters
264 void LyXText::removeRow(RowList::iterator rit)
266 /* FIXME: when we cache the bview, this should just
267 * become a postPaint(), I think */
268 if (refresh_row == rit) {
269 if (rit == rows().begin())
270 refresh_row = boost::next(rit);
272 refresh_row = boost::prior(rit);
274 // what about refresh_y
277 if (anchor_row_ == rit) {
278 if (rit != rows().begin()) {
279 anchor_row_ = boost::prior(rit);
280 anchor_row_offset_ += boost::prior(rit)->height();
282 anchor_row_ = boost::next(rit);
283 anchor_row_offset_ -= rit->height();
287 // the text becomes smaller
288 height -= rit->height();
294 // remove all following rows of the paragraph of the specified row.
295 void LyXText::removeParagraph(Row * row)
297 Paragraph * tmppar = row->par();
301 while (row && row->par() == tmppar) {
302 tmprow = row->next();
309 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
311 // insert a new row, starting at position 0
312 RowList::iterator rit = rowlist_.insert(rowit, new Row(par, 0));
314 // and now append the whole paragraph before the new row
315 appendParagraph(rit);
319 Inset * LyXText::getInset() const
321 if (cursor.pos() < cursor.par()->size()
322 && cursor.par()->isInset(cursor.pos())) {
323 return cursor.par()->getInset(cursor.pos());
329 void LyXText::toggleInset()
331 Inset * inset = getInset();
332 // is there an editable inset at cursor position?
333 if (!isEditableInset(inset)) {
334 // No, try to see if we are inside a collapsable inset
335 if (inset_owner && inset_owner->owner()
336 && inset_owner->owner()->isOpen()) {
337 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
338 inset_owner->owner()->close(bv());
339 bv()->getLyXText()->cursorRight(bv());
343 //bv()->owner()->message(inset->editMessage());
345 // do we want to keep this?? (JMarc)
346 if (!isHighlyEditableInset(inset))
347 setCursorParUndo(bv());
349 if (inset->isOpen()) {
355 bv()->updateInset(inset);
359 /* used in setlayout */
360 // Asger is not sure we want to do this...
361 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
364 LyXLayout_ptr const & layout = par.layout();
367 for (pos_type pos = 0; pos < par.size(); ++pos) {
368 if (pos < par.beginningOfBody())
369 layoutfont = layout->labelfont;
371 layoutfont = layout->font;
373 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
374 tmpfont.reduce(layoutfont);
375 par.setFont(pos, tmpfont);
380 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
381 LyXCursor & send_cur,
382 string const & layout)
384 Paragraph * endpar = send_cur.par()->next();
385 Paragraph * undoendpar = endpar;
387 if (endpar && endpar->getDepth()) {
388 while (endpar && endpar->getDepth()) {
389 endpar = endpar->next();
393 endpar = endpar->next(); // because of parindents etc.
396 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
398 // ok we have a selection. This is always between sstart_cur
399 // and sel_end cursor
401 Paragraph * par = sstart_cur.par();
402 Paragraph * epar = send_cur.par()->next();
404 LyXLayout_ptr const & lyxlayout =
405 bv()->buffer()->params.getLyXTextClass()[layout];
408 par->applyLayout(lyxlayout);
409 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
410 Paragraph * fppar = par;
411 fppar->params().spaceTop(lyxlayout->fill_top ?
412 VSpace(VSpace::VFILL)
413 : VSpace(VSpace::NONE));
414 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
415 VSpace(VSpace::VFILL)
416 : VSpace(VSpace::NONE));
417 if (lyxlayout->margintype == MARGIN_MANUAL)
418 par->setLabelWidthString(lyxlayout->labelstring());
421 } while (par != epar);
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;
438 Paragraph * endpar = setLayout(cursor, selection.start,
439 selection.end, layout);
440 redoParagraphs(selection.start, endpar);
442 // we have to reset the selection, because the
443 // geometry could have changed
444 setCursor(selection.start.par(),
445 selection.start.pos(), false);
446 selection.cursor = cursor;
447 setCursor(selection.end.par(), selection.end.pos(), false);
451 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
455 // increment depth over selection and
456 // make a total rebreak of those paragraphs
457 void LyXText::incDepth()
459 // If there is no selection, just use the current paragraph
460 if (!selection.set()) {
461 selection.start = cursor; // dummy selection
462 selection.end = cursor;
465 // We end at the next paragraph with depth 0
466 Paragraph * endpar = selection.end.par()->next();
468 Paragraph * undoendpar = endpar;
470 if (endpar && endpar->getDepth()) {
471 while (endpar && endpar->getDepth()) {
472 endpar = endpar->next();
476 endpar = endpar->next(); // because of parindents etc.
479 setUndo(bv(), Undo::EDIT,
480 selection.start.par(), undoendpar);
482 LyXCursor tmpcursor = cursor; // store the current cursor
484 // ok we have a selection. This is always between sel_start_cursor
485 // and sel_end cursor
486 cursor = selection.start;
489 // NOTE: you can't change the depth of a bibliography entry
490 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
491 Paragraph * prev = cursor.par()->previous();
494 if (cursor.par()->getDepth()
495 < prev->getMaxDepthAfter()) {
496 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
500 if (cursor.par() == selection.end.par())
502 cursor.par(cursor.par()->next());
505 redoParagraphs(selection.start, endpar);
507 // we have to reset visual the selection because the
508 // geometry could have changed
509 setCursor(selection.start.par(), selection.start.pos());
510 selection.cursor = cursor;
511 setCursor(selection.end.par(), selection.end.pos());
514 setCursor(tmpcursor.par(), tmpcursor.pos());
518 // decrement depth over selection and
519 // make a total rebreak of those paragraphs
520 void LyXText::decDepth()
522 // if there is no selection just set the layout
523 // of the current paragraph
524 if (!selection.set()) {
525 selection.start = cursor; // dummy selection
526 selection.end = cursor;
528 Paragraph * endpar = selection.end.par()->next();
529 Paragraph * undoendpar = endpar;
531 if (endpar && endpar->getDepth()) {
532 while (endpar && endpar->getDepth()) {
533 endpar = endpar->next();
537 endpar = endpar->next(); // because of parindents etc.
540 setUndo(bv(), Undo::EDIT,
541 selection.start.par(), undoendpar);
543 LyXCursor tmpcursor = cursor; // store the current cursor
545 // ok we have a selection. This is always between sel_start_cursor
546 // and sel_end cursor
547 cursor = selection.start;
550 if (cursor.par()->params().depth()) {
551 cursor.par()->params()
552 .depth(cursor.par()->params().depth() - 1);
554 if (cursor.par() == selection.end.par()) {
557 cursor.par(cursor.par()->next());
560 redoParagraphs(selection.start, endpar);
562 // we have to reset the visual selection because the
563 // geometry could have changed
564 setCursor(selection.start.par(), selection.start.pos());
565 selection.cursor = cursor;
566 setCursor(selection.end.par(), selection.end.pos());
569 setCursor(tmpcursor.par(), tmpcursor.pos());
573 // set font over selection and make a total rebreak of those paragraphs
574 void LyXText::setFont(LyXFont const & font, bool toggleall)
576 // if there is no selection just set the current_font
577 if (!selection.set()) {
578 // Determine basis font
580 if (cursor.pos() < cursor.par()->beginningOfBody()) {
581 layoutfont = getLabelFont(bv()->buffer(),
584 layoutfont = getLayoutFont(bv()->buffer(),
587 // Update current font
588 real_current_font.update(font,
589 bv()->buffer()->params.language,
592 // Reduce to implicit settings
593 current_font = real_current_font;
594 current_font.reduce(layoutfont);
595 // And resolve it completely
596 real_current_font.realize(layoutfont);
601 LyXCursor tmpcursor = cursor; // store the current cursor
603 // ok we have a selection. This is always between sel_start_cursor
604 // and sel_end cursor
606 setUndo(bv(), Undo::EDIT,
607 selection.start.par(), selection.end.par()->next());
609 cursor = selection.start;
610 while (cursor.par() != selection.end.par() ||
611 cursor.pos() < selection.end.pos())
613 if (cursor.pos() < cursor.par()->size()) {
614 // an open footnote should behave like a closed one
615 setCharFont(cursor.par(), cursor.pos(),
617 cursor.pos(cursor.pos() + 1);
620 cursor.par(cursor.par()->next());
625 redoParagraphs(selection.start, selection.end.par()->next());
627 // we have to reset the selection, because the
628 // geometry could have changed, but we keep
629 // it for user convenience
630 setCursor(selection.start.par(), selection.start.pos());
631 selection.cursor = cursor;
632 setCursor(selection.end.par(), selection.end.pos());
634 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
635 tmpcursor.boundary());
639 void LyXText::redoHeightOfParagraph()
641 Row * tmprow = cursor.row();
642 int y = cursor.y() - tmprow->baseline();
644 setHeightOfRow(tmprow);
646 while (tmprow->previous()
647 && tmprow->previous()->par() == tmprow->par()) {
648 tmprow = tmprow->previous();
649 y -= tmprow->height();
650 setHeightOfRow(tmprow);
655 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
659 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
661 Row * tmprow = cur.row();
663 int y = cur.y() - tmprow->baseline();
664 setHeightOfRow(tmprow);
666 while (tmprow->previous()
667 && tmprow->previous()->par() == tmprow->par()) {
668 tmprow = tmprow->previous();
669 y -= tmprow->height();
673 setCursor(cur.par(), cur.pos());
677 // deletes and inserts again all paragaphs between the cursor
678 // and the specified par
679 // This function is needed after SetLayout and SetFont etc.
680 void LyXText::redoParagraphs(LyXCursor const & cur,
681 Paragraph const * endpar)
683 Row * tmprow = cur.row();
685 int y = cur.y() - tmprow->baseline();
687 Paragraph * first_phys_par = 0;
688 if (!tmprow->previous()) {
689 // a trick/hack for UNDO
690 // This is needed because in an UNDO/REDO we could have changed
691 // the ownerParagrah() so the paragraph inside the row is NOT
692 // my really first par anymore. Got it Lars ;) (Jug 20011206)
693 first_phys_par = ownerParagraph();
695 first_phys_par = tmprow->par();
697 // Find first row of this paragraph.
698 while (tmprow->previous()
699 && tmprow->previous()->par() == first_phys_par)
701 tmprow = tmprow->previous();
702 y -= tmprow->height();
706 Row * prevrow = tmprow->previous();
708 // Remove all the rows until we reach endpar
709 Paragraph * tmppar = 0;
711 tmppar = tmprow->next()->par();
712 while (tmprow->next() && tmppar != endpar) {
713 removeRow(tmprow->next());
714 if (tmprow->next()) {
715 tmppar = tmprow->next()->par();
721 // Remove the first of the paragraphs rows.
722 // This is because tmprow->previous() can be 0
723 Row * tmprow2 = tmprow;
724 tmprow = tmprow->previous();
727 // Reinsert the paragraphs.
728 tmppar = first_phys_par;
732 insertParagraph(tmppar, rowlist_.begin());
734 insertParagraph(tmppar, tmprow->next());
739 tmprow = &*rows().begin();
741 while (tmprow->next()
742 && tmprow->next()->par() == tmppar) {
743 tmprow = tmprow->next();
745 tmppar = tmppar->next();
747 } while (tmppar && tmppar != endpar);
749 // this is because of layout changes
751 setHeightOfRow(prevrow);
752 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
754 setHeightOfRow(&*rows().begin());
755 const_cast<LyXText *>(this)->postPaint(0);
758 if (tmprow && tmprow->next())
759 setHeightOfRow(tmprow->next());
764 void LyXText::fullRebreak()
766 if (rows().empty()) {
770 if (need_break_row != rows().end()) {
771 breakAgain(need_break_row);
772 need_break_row = rows().end();
778 // important for the screen
781 // the cursor set functions have a special mechanism. When they
782 // realize, that you left an empty paragraph, they will delete it.
783 // They also delete the corresponding row
785 // need the selection cursor:
786 void LyXText::setSelection()
788 bool const lsel = selection.set();
790 if (!selection.set()) {
791 last_sel_cursor = selection.cursor;
792 selection.start = selection.cursor;
793 selection.end = selection.cursor;
798 // first the toggling area
799 if (cursor.y() < last_sel_cursor.y()
800 || (cursor.y() == last_sel_cursor.y()
801 && cursor.x() < last_sel_cursor.x())) {
802 toggle_end_cursor = last_sel_cursor;
803 toggle_cursor = cursor;
805 toggle_end_cursor = cursor;
806 toggle_cursor = last_sel_cursor;
809 last_sel_cursor = cursor;
811 // and now the whole selection
813 if (selection.cursor.par() == cursor.par())
814 if (selection.cursor.pos() < cursor.pos()) {
815 selection.end = cursor;
816 selection.start = selection.cursor;
818 selection.end = selection.cursor;
819 selection.start = cursor;
821 else if (selection.cursor.y() < cursor.y() ||
822 (selection.cursor.y() == cursor.y()
823 && selection.cursor.x() < cursor.x())) {
824 selection.end = cursor;
825 selection.start = selection.cursor;
828 selection.end = selection.cursor;
829 selection.start = cursor;
832 // a selection with no contents is not a selection
833 if (selection.start.par() == selection.end.par() &&
834 selection.start.pos() == selection.end.pos())
835 selection.set(false);
837 if (inset_owner && (selection.set() || lsel))
838 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
842 string const LyXText::selectionAsString(Buffer const * buffer,
845 if (!selection.set()) return string();
847 // should be const ...
848 Paragraph * startpar(selection.start.par());
849 Paragraph * endpar(selection.end.par());
850 pos_type const startpos(selection.start.pos());
851 pos_type const endpos(selection.end.pos());
853 if (startpar == endpar) {
854 return startpar->asString(buffer, startpos, endpos, label);
859 // First paragraph in selection
860 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
862 // The paragraphs in between (if any)
863 LyXCursor tmpcur(selection.start);
864 tmpcur.par(tmpcur.par()->next());
865 while (tmpcur.par() != endpar) {
866 result += tmpcur.par()->asString(buffer, 0,
867 tmpcur.par()->size(),
869 tmpcur.par(tmpcur.par()->next());
872 // Last paragraph in selection
873 result += endpar->asString(buffer, 0, endpos, label);
879 void LyXText::clearSelection()
881 selection.set(false);
882 selection.mark(false);
883 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
884 // reset this in the bv_owner!
885 if (bv_owner && bv_owner->text)
886 bv_owner->text->xsel_cache.set(false);
890 void LyXText::cursorHome()
892 setCursor(cursor.par(), cursor.row()->pos());
896 void LyXText::cursorEnd()
898 if (cursor.par()->empty())
901 if (!cursor.row()->next()
902 || cursor.row()->next()->par() != cursor.row()->par()) {
903 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
905 if (!cursor.par()->empty() &&
906 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
907 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
908 setCursor(cursor.par(), cursor.row()->lastPos());
910 setCursor(cursor.par(),
911 cursor.row()->lastPos() + 1);
917 void LyXText::cursorTop()
919 while (cursor.par()->previous())
920 cursor.par(cursor.par()->previous());
921 setCursor(cursor.par(), 0);
925 void LyXText::cursorBottom()
927 while (cursor.par()->next())
928 cursor.par(cursor.par()->next());
929 setCursor(cursor.par(), cursor.par()->size());
933 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
935 // If the mask is completely neutral, tell user
936 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
937 // Could only happen with user style
938 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
942 // Try implicit word selection
943 // If there is a change in the language the implicit word selection
945 LyXCursor resetCursor = cursor;
946 bool implicitSelection = (font.language() == ignore_language
947 && font.number() == LyXFont::IGNORE)
948 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
951 setFont(font, toggleall);
953 // Implicit selections are cleared afterwards
954 //and cursor is set to the original position.
955 if (implicitSelection) {
957 cursor = resetCursor;
958 setCursor(cursor.par(), cursor.pos());
959 selection.cursor = cursor;
962 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
966 string LyXText::getStringToIndex()
968 // Try implicit word selection
969 // If there is a change in the language the implicit word selection
971 LyXCursor const reset_cursor = cursor;
972 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
975 if (!selection.set())
976 bv()->owner()->message(_("Nothing to index!"));
977 else if (selection.start.par() != selection.end.par())
978 bv()->owner()->message(_("Cannot index more than one paragraph!"));
980 idxstring = selectionAsString(bv()->buffer(), false);
982 // Reset cursors to their original position.
983 cursor = reset_cursor;
984 setCursor(cursor.par(), cursor.pos());
985 selection.cursor = cursor;
987 // Clear the implicit selection.
988 if (implicitSelection)
995 // the DTP switches for paragraphs. LyX will store them in the first
996 // physicla paragraph. When a paragraph is broken, the top settings rest,
997 // the bottom settings are given to the new one. So I can make shure,
998 // they do not duplicate themself and you cannnot make dirty things with
1001 void LyXText::setParagraph(bool line_top, bool line_bottom,
1002 bool pagebreak_top, bool pagebreak_bottom,
1003 VSpace const & space_top,
1004 VSpace const & space_bottom,
1005 Spacing const & spacing,
1007 string const & labelwidthstring,
1010 LyXCursor tmpcursor = cursor;
1011 if (!selection.set()) {
1012 selection.start = cursor;
1013 selection.end = cursor;
1016 // make sure that the depth behind the selection are restored, too
1017 Paragraph * endpar = selection.end.par()->next();
1018 Paragraph * undoendpar = endpar;
1020 if (endpar && endpar->getDepth()) {
1021 while (endpar && endpar->getDepth()) {
1022 endpar = endpar->next();
1023 undoendpar = endpar;
1027 // because of parindents etc.
1028 endpar = endpar->next();
1031 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1034 Paragraph * tmppar = selection.end.par();
1036 while (tmppar != selection.start.par()->previous()) {
1037 setCursor(tmppar, 0);
1038 postPaint(cursor.y() - cursor.row()->baseline());
1039 cursor.par()->params().lineTop(line_top);
1040 cursor.par()->params().lineBottom(line_bottom);
1041 cursor.par()->params().pagebreakTop(pagebreak_top);
1042 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1043 cursor.par()->params().spaceTop(space_top);
1044 cursor.par()->params().spaceBottom(space_bottom);
1045 cursor.par()->params().spacing(spacing);
1046 // does the layout allow the new alignment?
1047 LyXLayout_ptr const & layout = cursor.par()->layout();
1049 if (align == LYX_ALIGN_LAYOUT)
1050 align = layout->align;
1051 if (align & layout->alignpossible) {
1052 if (align == layout->align)
1053 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1055 cursor.par()->params().align(align);
1057 cursor.par()->setLabelWidthString(labelwidthstring);
1058 cursor.par()->params().noindent(noindent);
1059 tmppar = cursor.par()->previous();
1062 redoParagraphs(selection.start, endpar);
1065 setCursor(selection.start.par(), selection.start.pos());
1066 selection.cursor = cursor;
1067 setCursor(selection.end.par(), selection.end.pos());
1069 setCursor(tmpcursor.par(), tmpcursor.pos());
1071 bv()->updateInset(inset_owner);
1075 // set the counter of a paragraph. This includes the labels
1076 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1078 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1079 LyXLayout_ptr const & layout = par->layout();
1081 if (par->previous()) {
1083 par->params().appendix(par->previous()->params().appendix());
1084 if (!par->params().appendix() && par->params().startOfAppendix()) {
1085 par->params().appendix(true);
1086 textclass.counters().reset();
1088 par->enumdepth = par->previous()->enumdepth;
1089 par->itemdepth = par->previous()->itemdepth;
1091 par->params().appendix(par->params().startOfAppendix());
1096 /* Maybe we have to increment the enumeration depth.
1097 * BUT, enumeration in a footnote is considered in isolation from its
1098 * surrounding paragraph so don't increment if this is the
1099 * first line of the footnote
1100 * AND, bibliographies can't have their depth changed ie. they
1101 * are always of depth 0
1104 && par->previous()->getDepth() < par->getDepth()
1105 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1106 && par->enumdepth < 3
1107 && layout->labeltype != LABEL_BIBLIO) {
1111 // Maybe we have to decrement the enumeration depth, see note above
1113 && par->previous()->getDepth() > par->getDepth()
1114 && layout->labeltype != LABEL_BIBLIO) {
1115 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1118 if (!par->params().labelString().empty()) {
1119 par->params().labelString(string());
1122 if (layout->margintype == MARGIN_MANUAL) {
1123 if (par->params().labelWidthString().empty()) {
1124 par->setLabelWidthString(layout->labelstring());
1127 par->setLabelWidthString(string());
1130 // is it a layout that has an automatic label?
1131 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1132 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1136 if (i >= 0 && i <= buf->params.secnumdepth) {
1140 textclass.counters().step(layout->latexname());
1142 // Is there a label? Useful for Chapter layout
1143 if (!par->params().appendix()) {
1144 s << layout->labelstring();
1146 s << layout->labelstring_appendix();
1149 // Use of an integer is here less than elegant. For now.
1150 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1151 if (!par->params().appendix()) {
1152 numbertype = "sectioning";
1154 numbertype = "appendix";
1155 if (par->isRightToLeftPar(buf->params))
1156 langtype = "hebrew";
1161 s << textclass.counters()
1162 .numberLabel(layout->latexname(),
1163 numbertype, langtype, head);
1165 par->params().labelString(STRCONV(s.str()));
1167 // reset enum counters
1168 textclass.counters().reset("enum");
1169 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1170 textclass.counters().reset("enum");
1171 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1173 // Yes I know this is a really, really! bad solution
1175 string enumcounter("enum");
1177 switch (par->enumdepth) {
1186 enumcounter += "iv";
1189 // not a valid enumdepth...
1193 textclass.counters().step(enumcounter);
1195 s << textclass.counters()
1196 .numberLabel(enumcounter, "enumeration");
1197 par->params().labelString(STRCONV(s.str()));
1199 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1200 textclass.counters().step("bibitem");
1201 int number = textclass.counters().value("bibitem");
1202 if (par->bibitem()) {
1203 par->bibitem()->setCounter(number);
1204 par->params().labelString(layout->labelstring());
1206 // In biblio should't be following counters but...
1208 string s = layout->labelstring();
1210 // the caption hack:
1211 if (layout->labeltype == LABEL_SENSITIVE) {
1212 Paragraph * tmppar = par;
1215 while (tmppar && tmppar->inInset()
1216 // the single '=' is intended below
1217 && (in = tmppar->inInset()->owner())) {
1218 if (in->lyxCode() == Inset::FLOAT_CODE ||
1219 in->lyxCode() == Inset::WRAP_CODE) {
1223 tmppar = in->parOwner();
1229 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1231 textclass.counters().step(fl.type());
1233 // Doesn't work... yet.
1234 #warning use boost.format
1235 #if USE_BOOST_FORMAT
1236 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1237 // s << boost::format(_("%1$s %1$d:")
1239 // % buf->counters().value(fl.name());
1242 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1243 o << fl.name() << " #:";
1244 s = STRCONV(o.str());
1247 // par->SetLayout(0);
1248 // s = layout->labelstring;
1249 s = _("Senseless: ");
1252 par->params().labelString(s);
1254 // reset the enumeration counter. They are always reset
1255 // when there is any other layout between
1256 // Just fall-through between the cases so that all
1257 // enum counters deeper than enumdepth is also reset.
1258 switch (par->enumdepth) {
1260 textclass.counters().reset("enumi");
1262 textclass.counters().reset("enumii");
1264 textclass.counters().reset("enumiii");
1266 textclass.counters().reset("enumiv");
1272 // Updates all counters. Paragraphs with changed label string will be rebroken
1273 void LyXText::updateCounters()
1275 RowList::iterator rowit = rows().begin();
1276 Paragraph * par = rowit->par();
1278 // CHECK if this is really needed. (Lgb)
1279 bv()->buffer()->params.getLyXTextClass().counters().reset();
1282 while (rowit->par() != par)
1285 string const oldLabel = par->params().labelString();
1287 // setCounter can potentially change the labelString.
1288 setCounter(bv()->buffer(), par);
1290 string const & newLabel = par->params().labelString();
1292 if (oldLabel.empty() && !newLabel.empty()) {
1293 removeParagraph(&*rowit);
1294 appendParagraph(rowit);
1302 void LyXText::insertInset(Inset * inset)
1304 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1306 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1308 cursor.par()->insertInset(cursor.pos(), inset);
1309 // Just to rebreak and refresh correctly.
1310 // The character will not be inserted a second time
1311 insertChar(Paragraph::META_INSET);
1312 // If we enter a highly editable inset the cursor should be to before
1313 // the inset. This couldn't happen before as Undo was not handled inside
1314 // inset now after the Undo LyX tries to call inset->Edit(...) again
1315 // and cannot do this as the cursor is behind the inset and GetInset
1316 // does not return the inset!
1317 if (isHighlyEditableInset(inset)) {
1324 void LyXText::copyEnvironmentType()
1326 copylayouttype = cursor.par()->layout()->name();
1330 void LyXText::pasteEnvironmentType()
1332 // do nothing if there has been no previous copyEnvironmentType()
1333 if (!copylayouttype.empty())
1334 setLayout(copylayouttype);
1338 void LyXText::cutSelection(bool doclear, bool realcut)
1340 // Stuff what we got on the clipboard. Even if there is no selection.
1342 // There is a problem with having the stuffing here in that the
1343 // larger the selection the slower LyX will get. This can be
1344 // solved by running the line below only when the selection has
1345 // finished. The solution used currently just works, to make it
1346 // faster we need to be more clever and probably also have more
1347 // calls to stuffClipboard. (Lgb)
1348 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1350 // This doesn't make sense, if there is no selection
1351 if (!selection.set())
1354 // OK, we have a selection. This is always between selection.start
1355 // and selection.end
1357 // make sure that the depth behind the selection are restored, too
1358 Paragraph * endpar = selection.end.par()->next();
1359 Paragraph * undoendpar = endpar;
1361 if (endpar && endpar->getDepth()) {
1362 while (endpar && endpar->getDepth()) {
1363 endpar = endpar->next();
1364 undoendpar = endpar;
1366 } else if (endpar) {
1367 endpar = endpar->next(); // because of parindents etc.
1370 setUndo(bv(), Undo::DELETE,
1371 selection.start.par(), undoendpar);
1373 // there are two cases: cut only within one paragraph or
1374 // more than one paragraph
1375 if (selection.start.par() == selection.end.par()) {
1376 // only within one paragraph
1377 endpar = selection.end.par();
1378 int pos = selection.end.pos();
1379 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1380 selection.start.pos(), pos,
1381 bv()->buffer()->params.textclass,
1383 selection.end.pos(pos);
1385 endpar = selection.end.par();
1386 int pos = selection.end.pos();
1387 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1388 selection.start.pos(), pos,
1389 bv()->buffer()->params.textclass,
1392 selection.end.par(endpar);
1393 selection.end.pos(pos);
1394 cursor.pos(selection.end.pos());
1396 endpar = endpar->next();
1398 // sometimes necessary
1400 selection.start.par()->stripLeadingSpaces();
1402 redoParagraphs(selection.start, endpar);
1404 // cutSelection can invalidate the cursor so we need to set
1406 // we prefer the end for when tracking changes
1407 cursor = selection.end;
1409 // need a valid cursor. (Lgb)
1412 setCursor(cursor.par(), cursor.pos());
1413 selection.cursor = cursor;
1418 void LyXText::copySelection()
1420 // stuff the selection onto the X clipboard, from an explicit copy request
1421 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1423 // this doesnt make sense, if there is no selection
1424 if (!selection.set())
1427 // ok we have a selection. This is always between selection.start
1428 // and sel_end cursor
1430 // copy behind a space if there is one
1431 while (selection.start.par()->size() > selection.start.pos()
1432 && selection.start.par()->isLineSeparator(selection.start.pos())
1433 && (selection.start.par() != selection.end.par()
1434 || selection.start.pos() < selection.end.pos()))
1435 selection.start.pos(selection.start.pos() + 1);
1437 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1438 selection.start.pos(), selection.end.pos(),
1439 bv()->buffer()->params.textclass);
1443 void LyXText::pasteSelection()
1445 // this does not make sense, if there is nothing to paste
1446 if (!CutAndPaste::checkPastePossible())
1449 setUndo(bv(), Undo::INSERT,
1450 cursor.par(), cursor.par()->next());
1453 Paragraph * actpar = cursor.par();
1454 int pos = cursor.pos();
1456 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1457 bv()->buffer()->params.textclass);
1459 redoParagraphs(cursor, endpar);
1461 setCursor(cursor.par(), cursor.pos());
1464 selection.cursor = cursor;
1465 setCursor(actpar, pos);
1471 void LyXText::setSelectionRange(lyx::pos_type length)
1476 selection.cursor = cursor;
1483 // simple replacing. The font of the first selected character is used
1484 void LyXText::replaceSelectionWithString(string const & str)
1486 setCursorParUndo(bv());
1489 if (!selection.set()) { // create a dummy selection
1490 selection.end = cursor;
1491 selection.start = cursor;
1494 // Get font setting before we cut
1495 pos_type pos = selection.end.pos();
1496 LyXFont const font = selection.start.par()
1497 ->getFontSettings(bv()->buffer()->params,
1498 selection.start.pos());
1500 // Insert the new string
1501 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1502 selection.end.par()->insertChar(pos, (*cit), font);
1506 // Cut the selection
1507 cutSelection(true, false);
1513 // needed to insert the selection
1514 void LyXText::insertStringAsLines(string const & str)
1516 Paragraph * par = cursor.par();
1517 pos_type pos = cursor.pos();
1518 Paragraph * endpar = cursor.par()->next();
1520 setCursorParUndo(bv());
1522 // only to be sure, should not be neccessary
1525 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1527 redoParagraphs(cursor, endpar);
1528 setCursor(cursor.par(), cursor.pos());
1529 selection.cursor = cursor;
1530 setCursor(par, pos);
1535 // turns double-CR to single CR, others where converted into one
1536 // blank. Then InsertStringAsLines is called
1537 void LyXText::insertStringAsParagraphs(string const & str)
1539 string linestr(str);
1540 bool newline_inserted = false;
1541 for (string::size_type i = 0; i < linestr.length(); ++i) {
1542 if (linestr[i] == '\n') {
1543 if (newline_inserted) {
1544 // we know that \r will be ignored by
1545 // InsertStringA. Of course, it is a dirty
1546 // trick, but it works...
1547 linestr[i - 1] = '\r';
1551 newline_inserted = true;
1553 } else if (IsPrintable(linestr[i])) {
1554 newline_inserted = false;
1557 insertStringAsLines(linestr);
1561 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1563 LyXCursor tmpcursor;
1567 RowList::iterator row = getRow(par, pos, y);
1568 RowList::iterator beg = rows().begin();
1570 // is there a break one row above
1572 && boost::prior(row)->par() == row->par()) {
1573 z = rowBreakPoint(*boost::prior(row));
1574 if (z >= row->pos()) {
1575 // set the dimensions of the row above
1576 y -= boost::prior(row)->height();
1579 breakAgain(&*boost::prior(row));
1581 // set the cursor again. Otherwise
1582 // dangling pointers are possible
1583 setCursor(cursor.par(), cursor.pos(),
1584 false, cursor.boundary());
1585 selection.cursor = cursor;
1590 int const tmpheight = row->height();
1591 pos_type const tmplast = row->lastPos();
1594 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1595 postRowPaint(&*row, y);
1600 // check the special right address boxes
1601 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1603 tmpcursor.row(&*row);
1608 redoDrawingOfParagraph(tmpcursor);
1611 // set the cursor again. Otherwise dangling pointers are possible
1612 // also set the selection
1614 if (selection.set()) {
1616 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1617 false, selection.cursor.boundary());
1618 selection.cursor = cursor;
1619 setCursorIntern(selection.start.par(),
1620 selection.start.pos(),
1621 false, selection.start.boundary());
1622 selection.start = cursor;
1623 setCursorIntern(selection.end.par(),
1624 selection.end.pos(),
1625 false, selection.end.boundary());
1626 selection.end = cursor;
1627 setCursorIntern(last_sel_cursor.par(),
1628 last_sel_cursor.pos(),
1629 false, last_sel_cursor.boundary());
1630 last_sel_cursor = cursor;
1633 setCursorIntern(cursor.par(), cursor.pos(),
1634 false, cursor.boundary());
1638 // returns false if inset wasn't found
1639 bool LyXText::updateInset(Inset * inset)
1641 // first check the current paragraph
1642 int pos = cursor.par()->getPositionOfInset(inset);
1644 checkParagraph(cursor.par(), pos);
1648 // check every paragraph
1650 Paragraph * par = ownerParagraph();
1652 pos = par->getPositionOfInset(inset);
1654 checkParagraph(par, pos);
1664 bool LyXText::setCursor(Paragraph * par,
1666 bool setfont, bool boundary)
1668 LyXCursor old_cursor = cursor;
1669 setCursorIntern(par, pos, setfont, boundary);
1670 return deleteEmptyParagraphMechanism(old_cursor);
1674 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1675 pos_type pos, bool boundary)
1681 cur.boundary(boundary);
1683 // get the cursor y position in text
1685 RowList::iterator row = getRow(par, pos, y);
1686 RowList::iterator beg = rows().begin();
1688 RowList::iterator old_row = row;
1690 // if we are before the first char of this row and are still in the
1691 // same paragraph and there is a previous row then put the cursor on
1692 // the end of the previous row
1693 cur.iy(y + row->baseline());
1695 if (row != beg && pos &&
1696 boost::prior(row)->par() == row->par() &&
1697 pos < par->size() &&
1698 par->getChar(pos) == Paragraph::META_INSET &&
1699 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1706 // y is now the beginning of the cursor row
1707 y += row->baseline();
1708 // y is now the cursor baseline
1711 pos_type last = old_row->lastPrintablePos();
1713 // None of these should happen, but we're scaredy-cats
1714 if (pos > par->size()) {
1715 lyxerr << "dont like 1 please report" << endl;
1718 } else if (pos > last + 1) {
1719 lyxerr << "dont like 2 please report" << endl;
1720 // This shouldn't happen.
1723 } else if (pos < row->pos()) {
1724 lyxerr << "dont like 3 please report" << endl;
1729 // now get the cursors x position
1730 float x = getCursorX(&*row, pos, last, boundary);
1733 if (old_row != row) {
1734 x = getCursorX(&*old_row, pos, last, boundary);
1738 //if the cursor is in a visible row, anchor to it
1740 if (topy < y && y < topy + bv()->workHeight())
1745 float LyXText::getCursorX(Row * row,
1746 pos_type pos, pos_type last, bool boundary) const
1748 pos_type cursor_vpos = 0;
1750 float fill_separator;
1752 float fill_label_hfill;
1753 // This call HAS to be here because of the BidiTables!!!
1754 prepareToPrint(row, x, fill_separator, fill_hfill,
1757 if (last < row->pos())
1758 cursor_vpos = row->pos();
1759 else if (pos > last && !boundary)
1760 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1761 ? row->pos() : last + 1;
1762 else if (pos > row->pos() &&
1763 (pos > last || boundary))
1764 /// Place cursor after char at (logical) position pos - 1
1765 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1766 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1768 /// Place cursor before char at (logical) position pos
1769 cursor_vpos = (bidi_level(pos) % 2 == 0)
1770 ? log2vis(pos) : log2vis(pos) + 1;
1772 pos_type body_pos = row->par()->beginningOfBody();
1773 if ((body_pos > 0) &&
1774 ((body_pos-1 > last) ||
1775 !row->par()->isLineSeparator(body_pos - 1)))
1778 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1779 pos_type pos = vis2log(vpos);
1780 if (body_pos > 0 && pos == body_pos - 1) {
1781 x += fill_label_hfill +
1782 font_metrics::width(
1783 row->par()->layout()->labelsep,
1784 getLabelFont(bv()->buffer(),
1786 if (row->par()->isLineSeparator(body_pos - 1))
1788 row->par(), body_pos - 1);
1790 if (row->hfillExpansion(pos)) {
1791 x += singleWidth(row->par(), pos);
1792 if (pos >= body_pos)
1795 x += fill_label_hfill;
1796 } else if (row->par()->isSeparator(pos)) {
1797 x += singleWidth(row->par(), pos);
1798 if (pos >= body_pos)
1799 x += fill_separator;
1801 x += singleWidth(row->par(), pos);
1807 void LyXText::setCursorIntern(Paragraph * par,
1808 pos_type pos, bool setfont, bool boundary)
1810 InsetText * it = static_cast<InsetText *>(par->inInset());
1812 if (it != inset_owner) {
1813 lyxerr[Debug::INSETS] << "InsetText is " << it
1815 << "inset_owner is "
1816 << inset_owner << endl;
1817 #ifdef WITH_WARNINGS
1818 #warning I believe this code is wrong. (Lgb)
1819 #warning Jürgen, have a look at this. (Lgb)
1820 #warning Hmmm, I guess you are right but we
1821 #warning should verify when this is needed
1823 // Jürgen, would you like to have a look?
1824 // I guess we need to move the outer cursor
1825 // and open and lock the inset (bla bla bla)
1826 // stuff I don't know... so can you have a look?
1828 // I moved the lyxerr stuff in here so we can see if
1829 // this is actually really needed and where!
1831 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1836 setCursor(cursor, par, pos, boundary);
1842 void LyXText::setCurrentFont()
1844 pos_type pos = cursor.pos();
1845 if (cursor.boundary() && pos > 0)
1849 if (pos == cursor.par()->size())
1851 else // potentional bug... BUG (Lgb)
1852 if (cursor.par()->isSeparator(pos)) {
1853 if (pos > cursor.row()->pos() &&
1854 bidi_level(pos) % 2 ==
1855 bidi_level(pos - 1) % 2)
1857 else if (pos + 1 < cursor.par()->size())
1863 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1864 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1866 if (cursor.pos() == cursor.par()->size() &&
1867 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1868 !cursor.boundary()) {
1869 Language const * lang =
1870 cursor.par()->getParLanguage(bv()->buffer()->params);
1871 current_font.setLanguage(lang);
1872 current_font.setNumber(LyXFont::OFF);
1873 real_current_font.setLanguage(lang);
1874 real_current_font.setNumber(LyXFont::OFF);
1879 // returns the column near the specified x-coordinate of the row
1880 // x is set to the real beginning of this column
1882 LyXText::getColumnNearX(Row * row, int & x,
1883 bool & boundary) const
1886 float fill_separator;
1888 float fill_label_hfill;
1890 prepareToPrint(row, tmpx, fill_separator,
1891 fill_hfill, fill_label_hfill);
1893 pos_type vc = row->pos();
1894 pos_type last = row->lastPrintablePos();
1897 LyXLayout_ptr const & layout = row->par()->layout();
1899 bool left_side = false;
1901 pos_type body_pos = row->par()->beginningOfBody();
1902 float last_tmpx = tmpx;
1905 (body_pos - 1 > last ||
1906 !row->par()->isLineSeparator(body_pos - 1)))
1909 // check for empty row
1910 if (!row->par()->size()) {
1915 while (vc <= last && tmpx <= x) {
1918 if (body_pos > 0 && c == body_pos-1) {
1919 tmpx += fill_label_hfill +
1920 font_metrics::width(layout->labelsep,
1921 getLabelFont(bv()->buffer(), row->par()));
1922 if (row->par()->isLineSeparator(body_pos - 1))
1923 tmpx -= singleWidth(row->par(), body_pos-1);
1926 if (row->hfillExpansion(c)) {
1927 tmpx += singleWidth(row->par(), c);
1931 tmpx += fill_label_hfill;
1932 } else if (row->par()->isSeparator(c)) {
1933 tmpx += singleWidth(row->par(), c);
1935 tmpx+= fill_separator;
1937 tmpx += singleWidth(row->par(), c);
1942 if ((tmpx + last_tmpx) / 2 > x) {
1947 if (vc > last + 1) // This shouldn't happen.
1951 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
1952 // some speedup if rtl_support=false
1953 && (!row->next() || row->next()->par() != row->par());
1954 bool const rtl = (lastrow)
1955 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
1956 : false; // If lastrow is false, we don't need to compute
1957 // the value of rtl.
1960 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
1961 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1963 else if (vc == row->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(), row->par(), c);
1976 if (row->pos() <= last && c > last
1977 && row->par()->isNewline(last)) {
1978 if (bidi_level(last) % 2 == 0)
1979 tmpx -= singleWidth(row->par(), last);
1981 tmpx += singleWidth(row->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(Row & row, LyXCursor & cur) {
2011 Row const & next = *row.next();
2013 if (next.pos() != cur.pos() || next.par() != cur.par())
2015 if (!cur.par()->isInset(cur.pos()))
2017 Inset const * inset = cur.par()->getInset(cur.pos());
2018 if (inset->needFullRow() || inset->display())
2025 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
2027 // Get the row first.
2029 RowList::iterator row = getRowNearY(y);
2031 pos_type const column = getColumnNearX(&*row, x, bound);
2032 cur.par(row->par());
2033 cur.pos(row->pos() + column);
2035 cur.y(y + row->baseline());
2038 if (beforeFullRowInset(*row, cur)) {
2039 pos_type last = row->lastPrintablePos();
2040 float x = getCursorX(row->next(), cur.pos(), last, bound);
2042 cur.iy(y + row->height() + row->next()->baseline());
2043 cur.irow(row->next());
2049 cur.boundary(bound);
2053 void LyXText::cursorLeft(bool internal)
2055 if (cursor.pos() > 0) {
2056 bool boundary = cursor.boundary();
2057 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2058 if (!internal && !boundary &&
2059 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2060 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2061 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2062 Paragraph * par = cursor.par()->previous();
2063 setCursor(par, par->size());
2068 void LyXText::cursorRight(bool internal)
2070 if (!internal && cursor.boundary() &&
2071 !cursor.par()->isNewline(cursor.pos()))
2072 setCursor(cursor.par(), cursor.pos(), true, false);
2073 else if (cursor.pos() < cursor.par()->size()) {
2074 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2076 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2077 setCursor(cursor.par(), cursor.pos(), true, true);
2078 } else if (cursor.par()->next())
2079 setCursor(cursor.par()->next(), 0);
2083 void LyXText::cursorUp(bool selecting)
2086 int x = cursor.x_fix();
2087 int y = cursor.y() - cursor.row()->baseline() - 1;
2088 setCursorFromCoordinates(x, y);
2091 int y1 = cursor.iy() - topy;
2094 Inset * inset_hit = checkInsetHit(x, y1);
2095 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2096 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2100 setCursorFromCoordinates(bv(), cursor.x_fix(),
2101 cursor.y() - cursor.row()->baseline() - 1);
2106 void LyXText::cursorDown(bool selecting)
2109 int x = cursor.x_fix();
2110 int y = cursor.y() - cursor.row()->baseline() +
2111 cursor.row()->height() + 1;
2112 setCursorFromCoordinates(x, y);
2113 if (!selecting && cursor.row() == cursor.irow()) {
2115 int y1 = cursor.iy() - topy;
2118 Inset * inset_hit = checkInsetHit(x, y1);
2119 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2120 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2124 setCursorFromCoordinates(bv(), cursor.x_fix(),
2125 cursor.y() - cursor.row()->baseline()
2126 + cursor.row()->height() + 1);
2131 void LyXText::cursorUpParagraph()
2133 if (cursor.pos() > 0) {
2134 setCursor(cursor.par(), 0);
2136 else if (cursor.par()->previous()) {
2137 setCursor(cursor.par()->previous(), 0);
2142 void LyXText::cursorDownParagraph()
2144 if (cursor.par()->next()) {
2145 setCursor(cursor.par()->next(), 0);
2147 setCursor(cursor.par(), cursor.par()->size());
2151 // fix the cursor `cur' after a characters has been deleted at `where'
2152 // position. Called by deleteEmptyParagraphMechanism
2153 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2154 LyXCursor const & where)
2156 // if cursor is not in the paragraph where the delete occured,
2158 if (cur.par() != where.par())
2161 // if cursor position is after the place where the delete occured,
2163 if (cur.pos() > where.pos())
2164 cur.pos(cur.pos()-1);
2166 // check also if we don't want to set the cursor on a spot behind the
2167 // pagragraph because we erased the last character.
2168 if (cur.pos() > cur.par()->size())
2169 cur.pos(cur.par()->size());
2171 // recompute row et al. for this cursor
2172 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2176 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2178 // Would be wrong to delete anything if we have a selection.
2179 if (selection.set())
2182 // We allow all kinds of "mumbo-jumbo" when freespacing.
2183 if (old_cursor.par()->layout()->free_spacing
2184 || old_cursor.par()->isFreeSpacing()) {
2188 /* Ok I'll put some comments here about what is missing.
2189 I have fixed BackSpace (and thus Delete) to not delete
2190 double-spaces automagically. I have also changed Cut,
2191 Copy and Paste to hopefully do some sensible things.
2192 There are still some small problems that can lead to
2193 double spaces stored in the document file or space at
2194 the beginning of paragraphs. This happens if you have
2195 the cursor betwenn to spaces and then save. Or if you
2196 cut and paste and the selection have a space at the
2197 beginning and then save right after the paste. I am
2198 sure none of these are very hard to fix, but I will
2199 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2200 that I can get some feedback. (Lgb)
2203 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2204 // delete the LineSeparator.
2207 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2208 // delete the LineSeparator.
2211 // If the pos around the old_cursor were spaces, delete one of them.
2212 if (old_cursor.par() != cursor.par()
2213 || old_cursor.pos() != cursor.pos()) {
2214 // Only if the cursor has really moved
2216 if (old_cursor.pos() > 0
2217 && old_cursor.pos() < old_cursor.par()->size()
2218 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2219 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2220 old_cursor.par()->erase(old_cursor.pos() - 1);
2221 redoParagraphs(old_cursor, old_cursor.par()->next());
2223 #ifdef WITH_WARNINGS
2224 #warning This will not work anymore when we have multiple views of the same buffer
2225 // In this case, we will have to correct also the cursors held by
2226 // other bufferviews. It will probably be easier to do that in a more
2227 // automated way in LyXCursor code. (JMarc 26/09/2001)
2229 // correct all cursors held by the LyXText
2230 fixCursorAfterDelete(cursor, old_cursor);
2231 fixCursorAfterDelete(selection.cursor,
2233 fixCursorAfterDelete(selection.start,
2235 fixCursorAfterDelete(selection.end, old_cursor);
2236 fixCursorAfterDelete(last_sel_cursor,
2238 fixCursorAfterDelete(toggle_cursor, old_cursor);
2239 fixCursorAfterDelete(toggle_end_cursor,
2245 // don't delete anything if this is the ONLY paragraph!
2246 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2249 // Do not delete empty paragraphs with keepempty set.
2250 if (old_cursor.par()->layout()->keepempty)
2253 // only do our magic if we changed paragraph
2254 if (old_cursor.par() == cursor.par())
2257 // record if we have deleted a paragraph
2258 // we can't possibly have deleted a paragraph before this point
2259 bool deleted = false;
2261 if ((old_cursor.par()->empty()
2262 || (old_cursor.par()->size() == 1
2263 && old_cursor.par()->isLineSeparator(0)))) {
2264 // ok, we will delete anything
2265 LyXCursor tmpcursor;
2269 if (old_cursor.row()->previous()) {
2270 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2271 - old_cursor.row()->previous()->height());
2273 cursor = old_cursor; // that undo can restore the right cursor position
2274 Paragraph * endpar = old_cursor.par()->next();
2275 if (endpar && endpar->getDepth()) {
2276 while (endpar && endpar->getDepth()) {
2277 endpar = endpar->next();
2280 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2284 removeRow(old_cursor.row());
2285 if (ownerParagraph() == old_cursor.par()) {
2286 ownerParagraph(ownerParagraph()->next());
2289 delete old_cursor.par();
2291 /* Breakagain the next par. Needed because of
2292 * the parindent that can occur or dissappear.
2293 * The next row can change its height, if
2294 * there is another layout before */
2295 if (refresh_row != rows().end()) {
2296 if (refresh_row->next()) {
2297 breakAgain(refresh_row->next());
2300 setHeightOfRow(refresh_row);
2303 Row * nextrow = old_cursor.row()->next();
2304 const_cast<LyXText *>(this)->postPaint(
2305 old_cursor.y() - old_cursor.row()->baseline());
2308 cursor = old_cursor; // that undo can restore the right cursor position
2309 Paragraph * endpar = old_cursor.par()->next();
2310 if (endpar && endpar->getDepth()) {
2311 while (endpar && endpar->getDepth()) {
2312 endpar = endpar->next();
2315 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2319 removeRow(old_cursor.row());
2321 if (ownerParagraph() == old_cursor.par()) {
2322 ownerParagraph(ownerParagraph()->next());
2325 delete old_cursor.par();
2327 /* Breakagain the next par. Needed because of
2328 the parindent that can occur or dissappear.
2329 The next row can change its height, if
2330 there is another layout before */
2332 breakAgain(nextrow);
2338 setCursorIntern(cursor.par(), cursor.pos());
2340 if (selection.cursor.par() == old_cursor.par()
2341 && selection.cursor.pos() == old_cursor.pos()) {
2342 // correct selection
2343 selection.cursor = cursor;
2347 if (old_cursor.par()->stripLeadingSpaces()) {
2348 redoParagraphs(old_cursor,
2349 old_cursor.par()->next());
2351 setCursorIntern(cursor.par(), cursor.pos());
2352 selection.cursor = cursor;
2359 Paragraph * LyXText::ownerParagraph() const
2362 return inset_owner->paragraph();
2364 return &*(bv_owner->buffer()->paragraphs.begin());
2368 void LyXText::ownerParagraph(Paragraph * p) const
2371 inset_owner->paragraph(p);
2373 bv_owner->buffer()->paragraphs.set(p);
2378 void LyXText::ownerParagraph(int id, Paragraph * p) const
2380 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2381 if (op && op->inInset()) {
2382 static_cast<InsetText *>(op->inInset())->paragraph(p);
2389 LyXText::refresh_status LyXText::refreshStatus() const
2391 return refresh_status_;
2395 void LyXText::clearPaint()
2397 refresh_status_ = REFRESH_NONE;
2398 refresh_row = rows().end();
2403 void LyXText::postPaint(int start_y)
2405 refresh_status old = refresh_status_;
2407 refresh_status_ = REFRESH_AREA;
2408 refresh_row = rows().end();
2410 if (old != REFRESH_NONE && refresh_y < start_y)
2413 refresh_y = start_y;
2418 // We are an inset's lyxtext. Tell the top-level lyxtext
2419 // it needs to update the row we're in.
2420 LyXText * t = bv()->text;
2421 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2425 // FIXME: we should probably remove this y parameter,
2426 // make refresh_y be 0, and use row->y etc.
2427 void LyXText::postRowPaint(Row * row, int start_y)
2429 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2430 refresh_status_ = REFRESH_AREA;
2433 refresh_y = start_y;
2436 if (refresh_status_ == REFRESH_AREA)
2439 refresh_status_ = REFRESH_ROW;
2445 // We are an inset's lyxtext. Tell the top-level lyxtext
2446 // it needs to update the row we're in.
2447 LyXText * t = bv()->text;
2448 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2452 bool LyXText::isInInset() const
2454 // Sub-level has non-null bv owner and
2455 // non-null inset owner.
2456 return inset_owner != 0 && bv_owner != 0;
2460 int defaultRowHeight()
2462 LyXFont const font(LyXFont::ALL_SANE);
2463 return int(font_metrics::maxAscent(font)
2464 + font_metrics::maxDescent(font) * 1.5);