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/inseterror.h"
35 #include "insets/insetbibitem.h"
36 #include "insets/insetspecialchar.h"
37 #include "insets/insettext.h"
38 #include "insets/insetfloat.h"
39 #include "insets/insetwrap.h"
41 #include "support/LAssert.h"
42 #include "support/textutils.h"
43 #include "support/lstrings.h"
45 #include "support/BoostFormat.h"
55 LyXText::LyXText(BufferView * bv)
56 : height(0), width(0), anchor_row_offset_(0),
57 inset_owner(0), the_locking_inset(0), bv_owner(bv)
59 anchor_row_ = rows().end();
60 need_break_row = rows().end();
61 refresh_row = rows().end();
67 LyXText::LyXText(BufferView * bv, InsetText * inset)
68 : height(0), width(0), anchor_row_offset_(0),
69 inset_owner(inset), the_locking_inset(0), bv_owner(bv)
71 anchor_row_ = rows().end();
72 need_break_row = rows().end();
73 refresh_row = rows().end();
79 void LyXText::init(BufferView * bview, bool reinit)
83 need_break_row = rows().end();
85 copylayouttype.erase();
88 } else if (!rowlist_.empty())
91 ParagraphList::iterator par = ownerParagraphs().begin();
92 ParagraphList::iterator end = ownerParagraphs().end();
94 current_font = getFont(bview->buffer(), &*par, 0);
96 for (; par != end; ++par) {
97 insertParagraph(&*par, rowlist_.end());
99 setCursorIntern(&*rowlist_.begin()->par(), 0);
100 selection.cursor = cursor;
108 LyXFont const realizeFont(LyXFont const & font,
112 LyXTextClass const & tclass = buf->params.getLyXTextClass();
113 LyXFont tmpfont(font);
114 Paragraph::depth_type par_depth = par->getDepth();
116 // Resolve against environment font information
117 while (par && par_depth && !tmpfont.resolved()) {
118 par = par->outerHook();
120 tmpfont.realize(par->layout()->font);
121 par_depth = par->getDepth();
125 tmpfont.realize(tclass.defaultfont());
133 // Gets the fully instantiated font at a given position in a paragraph
134 // Basically the same routine as Paragraph::getFont() in paragraph.C.
135 // The difference is that this one is used for displaying, and thus we
136 // are allowed to make cosmetic improvements. For instance make footnotes
138 // If position is -1, we get the layout font of the paragraph.
139 // If position is -2, we get the font of the manual label of the paragraph.
140 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
143 lyx::Assert(pos >= 0);
145 LyXLayout_ptr const & layout = par->layout();
147 // We specialize the 95% common case:
148 if (!par->getDepth()) {
149 if (layout->labeltype == LABEL_MANUAL
150 && pos < par->beginningOfBody()) {
152 LyXFont f = par->getFontSettings(buf->params, pos);
154 par->inInset()->getDrawFont(f);
155 return f.realize(layout->reslabelfont);
157 LyXFont f = par->getFontSettings(buf->params, pos);
159 par->inInset()->getDrawFont(f);
160 return f.realize(layout->resfont);
164 // The uncommon case need not be optimized as much
168 if (pos < par->beginningOfBody()) {
170 layoutfont = layout->labelfont;
173 layoutfont = layout->font;
176 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
177 tmpfont.realize(layoutfont);
180 par->inInset()->getDrawFont(tmpfont);
182 return realizeFont(tmpfont, buf, par);
186 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
188 LyXLayout_ptr const & layout = par->layout();
190 if (!par->getDepth()) {
191 return layout->resfont;
194 return realizeFont(layout->font, buf, par);
198 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
200 LyXLayout_ptr const & layout = par->layout();
202 if (!par->getDepth()) {
203 return layout->reslabelfont;
206 return realizeFont(layout->labelfont, buf, par);
210 void LyXText::setCharFont(Paragraph * par,
211 pos_type pos, LyXFont const & fnt,
214 Buffer const * buf = bv()->buffer();
215 LyXFont font = getFont(buf, par, pos);
216 font.update(fnt, buf->params.language, toggleall);
217 // Let the insets convert their font
218 if (par->isInset(pos)) {
219 Inset * inset = par->getInset(pos);
220 if (isEditableInset(inset)) {
221 UpdatableInset * uinset =
222 static_cast<UpdatableInset *>(inset);
223 uinset->setFont(bv(), fnt, toggleall, true);
227 // Plug thru to version below:
228 setCharFont(buf, par, pos, font);
232 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
233 pos_type pos, LyXFont const & fnt)
237 LyXTextClass const & tclass = buf->params.getLyXTextClass();
238 LyXLayout_ptr const & layout = par->layout();
240 // Get concrete layout font to reduce against
243 if (pos < par->beginningOfBody())
244 layoutfont = layout->labelfont;
246 layoutfont = layout->font;
248 // Realize against environment font information
249 if (par->getDepth()) {
250 Paragraph * tp = par;
251 while (!layoutfont.resolved() && tp && tp->getDepth()) {
252 tp = tp->outerHook();
254 layoutfont.realize(tp->layout()->font);
258 layoutfont.realize(tclass.defaultfont());
260 // Now, reduce font against full layout font
261 font.reduce(layoutfont);
263 par->setFont(pos, font);
267 // removes the row and reset the touched counters
268 void LyXText::removeRow(RowList::iterator rit)
270 /* FIXME: when we cache the bview, this should just
271 * become a postPaint(), I think */
272 if (refresh_row == rit) {
273 if (rit == rows().begin())
274 refresh_row = boost::next(rit);
276 refresh_row = boost::prior(rit);
278 // what about refresh_y
281 if (anchor_row_ == rit) {
282 if (rit != rows().begin()) {
283 anchor_row_ = boost::prior(rit);
284 anchor_row_offset_ += boost::prior(rit)->height();
286 anchor_row_ = boost::next(rit);
287 anchor_row_offset_ -= rit->height();
291 // the text becomes smaller
292 height -= rit->height();
298 // remove all following rows of the paragraph of the specified row.
299 void LyXText::removeParagraph(RowList::iterator rit)
301 ParagraphList::iterator tmppit = rit->par();
304 while (rit != rows().end() && rit->par() == tmppit) {
305 RowList::iterator tmprit = boost::next(rit);
312 #warning FIXME Convert this to ParagraphList::iterator
313 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
315 // insert a new row, starting at position 0
317 RowList::iterator rit = rowlist_.insert(rowit, newrow);
319 // and now append the whole paragraph before the new row
320 appendParagraph(rit);
324 Inset * LyXText::getInset() const
326 if (cursor.pos() < cursor.par()->size()
327 && cursor.par()->isInset(cursor.pos())) {
328 return cursor.par()->getInset(cursor.pos());
334 void LyXText::toggleInset()
336 Inset * inset = getInset();
337 // is there an editable inset at cursor position?
338 if (!isEditableInset(inset)) {
339 // No, try to see if we are inside a collapsable inset
340 if (inset_owner && inset_owner->owner()
341 && inset_owner->owner()->isOpen()) {
342 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
343 inset_owner->owner()->close(bv());
344 bv()->getLyXText()->cursorRight(bv());
348 //bv()->owner()->message(inset->editMessage());
350 // do we want to keep this?? (JMarc)
351 if (!isHighlyEditableInset(inset))
352 setCursorParUndo(bv());
354 if (inset->isOpen()) {
360 bv()->updateInset(inset);
364 /* used in setlayout */
365 // Asger is not sure we want to do this...
366 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
369 LyXLayout_ptr const & layout = par.layout();
372 for (pos_type pos = 0; pos < par.size(); ++pos) {
373 if (pos < par.beginningOfBody())
374 layoutfont = layout->labelfont;
376 layoutfont = layout->font;
378 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
379 tmpfont.reduce(layoutfont);
380 par.setFont(pos, tmpfont);
385 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
386 LyXCursor & send_cur,
387 string const & layout)
389 Paragraph * endpar = send_cur.par()->next();
390 Paragraph * undoendpar = endpar;
392 if (endpar && endpar->getDepth()) {
393 while (endpar && endpar->getDepth()) {
394 endpar = endpar->next();
398 endpar = endpar->next(); // because of parindents etc.
401 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
403 // ok we have a selection. This is always between sstart_cur
404 // and sel_end cursor
406 Paragraph * par = sstart_cur.par();
407 Paragraph * epar = send_cur.par()->next();
409 LyXLayout_ptr const & lyxlayout =
410 bv()->buffer()->params.getLyXTextClass()[layout];
413 par->applyLayout(lyxlayout);
414 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
415 Paragraph * fppar = par;
416 fppar->params().spaceTop(lyxlayout->fill_top ?
417 VSpace(VSpace::VFILL)
418 : VSpace(VSpace::NONE));
419 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
420 VSpace(VSpace::VFILL)
421 : VSpace(VSpace::NONE));
422 if (lyxlayout->margintype == MARGIN_MANUAL)
423 par->setLabelWidthString(lyxlayout->labelstring());
426 } while (par != epar);
432 // set layout over selection and make a total rebreak of those paragraphs
433 void LyXText::setLayout(string const & layout)
435 LyXCursor tmpcursor = cursor; /* store the current cursor */
437 // if there is no selection just set the layout
438 // of the current paragraph */
439 if (!selection.set()) {
440 selection.start = cursor; // dummy selection
441 selection.end = cursor;
443 Paragraph * endpar = setLayout(cursor, selection.start,
444 selection.end, layout);
445 redoParagraphs(selection.start, endpar);
447 // we have to reset the selection, because the
448 // geometry could have changed
449 setCursor(selection.start.par(),
450 selection.start.pos(), false);
451 selection.cursor = cursor;
452 setCursor(selection.end.par(), selection.end.pos(), false);
456 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
460 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
462 ParagraphList::iterator pit(cursor.par());
463 ParagraphList::iterator end(cursor.par());
464 ParagraphList::iterator start = pit;
466 if (selection.set()) {
467 pit = selection.start.par();
468 end = selection.end.par();
472 ParagraphList::iterator pastend = end;
474 setUndo(bv(), Undo::EDIT, &(*start), &(*pastend));
476 int prev_after_depth = 0;
477 #warning parlist ... could be nicer ?
478 if (start != ownerParagraphs().begin())
479 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
482 int const depth = pit->params().depth();
483 if (type == bv_funcs::INC_DEPTH) {
484 if (depth < prev_after_depth
485 && pit->layout()->labeltype != LABEL_BIBLIO) {
486 pit->params().depth(depth + 1);
490 pit->params().depth(depth - 1);
493 prev_after_depth = pit->getMaxDepthAfter();
501 // Wow, redoParagraphs is stupid.
503 setCursor(tmpcursor, &(*start), 0);
504 redoParagraphs(tmpcursor, &(*pastend));
506 // We need to actually move the text->cursor. I don't
507 // understand why ...
510 // we have to reset the visual selection because the
511 // geometry could have changed
512 if (selection.set()) {
513 setCursor(selection.start.par(), selection.start.pos());
514 selection.cursor = cursor;
515 setCursor(selection.end.par(), selection.end.pos());
520 setCursor(tmpcursor.par(), tmpcursor.pos());
524 // set font over selection and make a total rebreak of those paragraphs
525 void LyXText::setFont(LyXFont const & font, bool toggleall)
527 // if there is no selection just set the current_font
528 if (!selection.set()) {
529 // Determine basis font
531 if (cursor.pos() < cursor.par()->beginningOfBody()) {
532 layoutfont = getLabelFont(bv()->buffer(),
535 layoutfont = getLayoutFont(bv()->buffer(),
538 // Update current font
539 real_current_font.update(font,
540 bv()->buffer()->params.language,
543 // Reduce to implicit settings
544 current_font = real_current_font;
545 current_font.reduce(layoutfont);
546 // And resolve it completely
547 real_current_font.realize(layoutfont);
552 LyXCursor tmpcursor = cursor; // store the current cursor
554 // ok we have a selection. This is always between sel_start_cursor
555 // and sel_end cursor
557 setUndo(bv(), Undo::EDIT,
558 selection.start.par(), selection.end.par()->next());
560 cursor = selection.start;
561 while (cursor.par() != selection.end.par() ||
562 cursor.pos() < selection.end.pos())
564 if (cursor.pos() < cursor.par()->size()) {
565 // an open footnote should behave like a closed one
566 setCharFont(cursor.par(), cursor.pos(),
568 cursor.pos(cursor.pos() + 1);
571 cursor.par(cursor.par()->next());
576 redoParagraphs(selection.start, selection.end.par()->next());
578 // we have to reset the selection, because the
579 // geometry could have changed, but we keep
580 // it for user convenience
581 setCursor(selection.start.par(), selection.start.pos());
582 selection.cursor = cursor;
583 setCursor(selection.end.par(), selection.end.pos());
585 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
586 tmpcursor.boundary());
590 void LyXText::redoHeightOfParagraph()
592 RowList::iterator tmprow = cursor.row();
593 int y = cursor.y() - tmprow->baseline();
595 setHeightOfRow(tmprow);
597 while (tmprow != rows().begin()
598 && boost::prior(tmprow)->par() == tmprow->par()) {
600 y -= tmprow->height();
601 setHeightOfRow(tmprow);
606 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
610 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
612 RowList::iterator tmprow = cur.row();
614 int y = cur.y() - tmprow->baseline();
615 setHeightOfRow(tmprow);
617 while (tmprow != rows().begin()
618 && boost::prior(tmprow)->par() == tmprow->par()) {
620 y -= tmprow->height();
624 setCursor(cur.par(), cur.pos());
628 // deletes and inserts again all paragaphs between the cursor
629 // and the specified par
630 // This function is needed after SetLayout and SetFont etc.
631 void LyXText::redoParagraphs(LyXCursor const & cur,
632 Paragraph const * ep)
634 RowList::iterator tmprit = cur.row();
635 ParagraphList::iterator endpit(const_cast<Paragraph*>(ep));
636 int y = cur.y() - tmprit->baseline();
638 ParagraphList::iterator first_phys_pit;
639 if (tmprit == rows().begin()) {
640 // A trick/hack for UNDO.
641 // This is needed because in an UNDO/REDO we could have
642 // changed the ownerParagrah() so the paragraph inside
643 // the row is NOT my really first par anymore.
644 // Got it Lars ;) (Jug 20011206)
645 first_phys_pit = ownerParagraphs().begin();
647 // In here prevrit could be set to rows().end(). (Lgb)
649 first_phys_pit = tmprit->par();
650 while (tmprit != rows().begin()
651 && boost::prior(tmprit)->par() == first_phys_pit)
654 y -= tmprit->height();
657 // Is it possible to put the prevrit setting in here? (Lgb)
660 RowList::iterator prevrit;
661 bool good_prevrit = false;
663 // It seems to mee that good_prevrit is not needed if we let
664 // a bad prevrit have the value rows().end() (Lgb)
665 if (tmprit != rows().begin()) {
666 prevrit = boost::prior(tmprit);
671 while (tmprit != rows().end() && tmprit->par() != endpit) {
672 RowList::iterator tmprit2 = tmprit++;
676 // Reinsert the paragraphs.
677 ParagraphList::iterator tmppit = first_phys_pit;
679 // See if this loop can be rewritten as a while loop instead.
680 // That should also make the code a bit easier to read. (Lgb)
682 if (tmppit != ownerParagraphs().end()) {
683 insertParagraph(&*tmppit, tmprit);
684 while (tmprit != rows().end()
685 && tmprit->par() == tmppit) {
690 } while (tmppit != ownerParagraphs().end() && tmppit != endpit);
693 // If the above changes are done, then we can compare prevrit
694 // with rows().end() here. (Lgb)
696 setHeightOfRow(prevrit);
697 const_cast<LyXText *>(this)->postPaint(y - prevrit->height());
699 setHeightOfRow(rows().begin());
700 const_cast<LyXText *>(this)->postPaint(0);
702 if (tmprit != rows().end())
703 setHeightOfRow(tmprit);
708 void LyXText::fullRebreak()
710 if (rows().empty()) {
714 if (need_break_row != rows().end()) {
715 breakAgain(need_break_row);
716 need_break_row = rows().end();
722 // important for the screen
725 // the cursor set functions have a special mechanism. When they
726 // realize, that you left an empty paragraph, they will delete it.
727 // They also delete the corresponding row
729 // need the selection cursor:
730 void LyXText::setSelection()
732 bool const lsel = selection.set();
734 if (!selection.set()) {
735 last_sel_cursor = selection.cursor;
736 selection.start = selection.cursor;
737 selection.end = selection.cursor;
742 // first the toggling area
743 if (cursor.y() < last_sel_cursor.y()
744 || (cursor.y() == last_sel_cursor.y()
745 && cursor.x() < last_sel_cursor.x())) {
746 toggle_end_cursor = last_sel_cursor;
747 toggle_cursor = cursor;
749 toggle_end_cursor = cursor;
750 toggle_cursor = last_sel_cursor;
753 last_sel_cursor = cursor;
755 // and now the whole selection
757 if (selection.cursor.par() == cursor.par())
758 if (selection.cursor.pos() < cursor.pos()) {
759 selection.end = cursor;
760 selection.start = selection.cursor;
762 selection.end = selection.cursor;
763 selection.start = cursor;
765 else if (selection.cursor.y() < cursor.y() ||
766 (selection.cursor.y() == cursor.y()
767 && selection.cursor.x() < cursor.x())) {
768 selection.end = cursor;
769 selection.start = selection.cursor;
772 selection.end = selection.cursor;
773 selection.start = cursor;
776 // a selection with no contents is not a selection
777 if (selection.start.par() == selection.end.par() &&
778 selection.start.pos() == selection.end.pos())
779 selection.set(false);
781 if (inset_owner && (selection.set() || lsel))
782 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
786 string const LyXText::selectionAsString(Buffer const * buffer,
789 if (!selection.set()) return string();
791 // should be const ...
792 Paragraph * startpar(selection.start.par());
793 Paragraph * endpar(selection.end.par());
794 pos_type const startpos(selection.start.pos());
795 pos_type const endpos(selection.end.pos());
797 if (startpar == endpar) {
798 return startpar->asString(buffer, startpos, endpos, label);
803 // First paragraph in selection
804 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
806 // The paragraphs in between (if any)
807 LyXCursor tmpcur(selection.start);
808 tmpcur.par(tmpcur.par()->next());
809 while (tmpcur.par() != endpar) {
810 result += tmpcur.par()->asString(buffer, 0,
811 tmpcur.par()->size(),
813 tmpcur.par(tmpcur.par()->next());
816 // Last paragraph in selection
817 result += endpar->asString(buffer, 0, endpos, label);
823 void LyXText::clearSelection()
825 selection.set(false);
826 selection.mark(false);
827 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
828 // reset this in the bv_owner!
829 if (bv_owner && bv_owner->text)
830 bv_owner->text->xsel_cache.set(false);
834 void LyXText::cursorHome()
836 setCursor(cursor.par(), cursor.row()->pos());
840 void LyXText::cursorEnd()
842 if (cursor.par()->empty())
845 // There is a lot of unneeded recalculation going on here:
846 // - boost::next(curosr.row())
847 // - lastPost(*this, cursor.row())
849 if (boost::next(cursor.row()) == rows().end()
850 || boost::next(cursor.row())->par() != cursor.row()->par()) {
851 setCursor(cursor.par(), lastPos(*this, cursor.row()) + 1);
853 if (!cursor.par()->empty() &&
854 (cursor.par()->getChar(lastPos(*this, cursor.row())) == ' '
855 || cursor.par()->isNewline(lastPos(*this, cursor.row())))) {
856 setCursor(cursor.par(), lastPos(*this, cursor.row()));
858 setCursor(cursor.par(),
859 lastPos(*this, cursor.row()) + 1);
865 void LyXText::cursorTop()
867 while (cursor.par()->previous())
868 cursor.par(cursor.par()->previous());
869 setCursor(cursor.par(), 0);
873 void LyXText::cursorBottom()
875 while (cursor.par()->next())
876 cursor.par(cursor.par()->next());
877 setCursor(cursor.par(), cursor.par()->size());
881 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
883 // If the mask is completely neutral, tell user
884 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
885 // Could only happen with user style
886 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
890 // Try implicit word selection
891 // If there is a change in the language the implicit word selection
893 LyXCursor resetCursor = cursor;
894 bool implicitSelection = (font.language() == ignore_language
895 && font.number() == LyXFont::IGNORE)
896 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
899 setFont(font, toggleall);
901 // Implicit selections are cleared afterwards
902 //and cursor is set to the original position.
903 if (implicitSelection) {
905 cursor = resetCursor;
906 setCursor(cursor.par(), cursor.pos());
907 selection.cursor = cursor;
910 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
914 string LyXText::getStringToIndex()
916 // Try implicit word selection
917 // If there is a change in the language the implicit word selection
919 LyXCursor const reset_cursor = cursor;
920 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
923 if (!selection.set())
924 bv()->owner()->message(_("Nothing to index!"));
925 else if (selection.start.par() != selection.end.par())
926 bv()->owner()->message(_("Cannot index more than one paragraph!"));
928 idxstring = selectionAsString(bv()->buffer(), false);
930 // Reset cursors to their original position.
931 cursor = reset_cursor;
932 setCursor(cursor.par(), cursor.pos());
933 selection.cursor = cursor;
935 // Clear the implicit selection.
936 if (implicitSelection)
943 // the DTP switches for paragraphs. LyX will store them in the first
944 // physicla paragraph. When a paragraph is broken, the top settings rest,
945 // the bottom settings are given to the new one. So I can make shure,
946 // they do not duplicate themself and you cannnot make dirty things with
949 void LyXText::setParagraph(bool line_top, bool line_bottom,
950 bool pagebreak_top, bool pagebreak_bottom,
951 VSpace const & space_top,
952 VSpace const & space_bottom,
953 Spacing const & spacing,
955 string const & labelwidthstring,
958 LyXCursor tmpcursor = cursor;
959 if (!selection.set()) {
960 selection.start = cursor;
961 selection.end = cursor;
964 // make sure that the depth behind the selection are restored, too
965 Paragraph * endpar = selection.end.par()->next();
966 Paragraph * undoendpar = endpar;
968 if (endpar && endpar->getDepth()) {
969 while (endpar && endpar->getDepth()) {
970 endpar = endpar->next();
975 // because of parindents etc.
976 endpar = endpar->next();
979 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
982 Paragraph * tmppar = selection.end.par();
984 while (tmppar != selection.start.par()->previous()) {
985 setCursor(tmppar, 0);
986 postPaint(cursor.y() - cursor.row()->baseline());
987 cursor.par()->params().lineTop(line_top);
988 cursor.par()->params().lineBottom(line_bottom);
989 cursor.par()->params().pagebreakTop(pagebreak_top);
990 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
991 cursor.par()->params().spaceTop(space_top);
992 cursor.par()->params().spaceBottom(space_bottom);
993 cursor.par()->params().spacing(spacing);
994 // does the layout allow the new alignment?
995 LyXLayout_ptr const & layout = cursor.par()->layout();
997 if (align == LYX_ALIGN_LAYOUT)
998 align = layout->align;
999 if (align & layout->alignpossible) {
1000 if (align == layout->align)
1001 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1003 cursor.par()->params().align(align);
1005 cursor.par()->setLabelWidthString(labelwidthstring);
1006 cursor.par()->params().noindent(noindent);
1007 tmppar = cursor.par()->previous();
1010 redoParagraphs(selection.start, endpar);
1013 setCursor(selection.start.par(), selection.start.pos());
1014 selection.cursor = cursor;
1015 setCursor(selection.end.par(), selection.end.pos());
1017 setCursor(tmpcursor.par(), tmpcursor.pos());
1019 bv()->updateInset(inset_owner);
1023 // set the counter of a paragraph. This includes the labels
1024 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1026 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1027 LyXLayout_ptr const & layout = par->layout();
1029 if (par->previous()) {
1031 par->params().appendix(par->previous()->params().appendix());
1032 if (!par->params().appendix() && par->params().startOfAppendix()) {
1033 par->params().appendix(true);
1034 textclass.counters().reset();
1036 par->enumdepth = par->previous()->enumdepth;
1037 par->itemdepth = par->previous()->itemdepth;
1039 par->params().appendix(par->params().startOfAppendix());
1044 /* Maybe we have to increment the enumeration depth.
1045 * BUT, enumeration in a footnote is considered in isolation from its
1046 * surrounding paragraph so don't increment if this is the
1047 * first line of the footnote
1048 * AND, bibliographies can't have their depth changed ie. they
1049 * are always of depth 0
1052 && par->previous()->getDepth() < par->getDepth()
1053 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1054 && par->enumdepth < 3
1055 && layout->labeltype != LABEL_BIBLIO) {
1059 // Maybe we have to decrement the enumeration depth, see note above
1061 && par->previous()->getDepth() > par->getDepth()
1062 && layout->labeltype != LABEL_BIBLIO) {
1063 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1066 if (!par->params().labelString().empty()) {
1067 par->params().labelString(string());
1070 if (layout->margintype == MARGIN_MANUAL) {
1071 if (par->params().labelWidthString().empty()) {
1072 par->setLabelWidthString(layout->labelstring());
1075 par->setLabelWidthString(string());
1078 // is it a layout that has an automatic label?
1079 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1080 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1084 if (i >= 0 && i <= buf->params.secnumdepth) {
1088 textclass.counters().step(layout->latexname());
1090 // Is there a label? Useful for Chapter layout
1091 if (!par->params().appendix()) {
1092 s << layout->labelstring();
1094 s << layout->labelstring_appendix();
1097 // Use of an integer is here less than elegant. For now.
1098 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1099 if (!par->params().appendix()) {
1100 numbertype = "sectioning";
1102 numbertype = "appendix";
1103 if (par->isRightToLeftPar(buf->params))
1104 langtype = "hebrew";
1109 s << textclass.counters()
1110 .numberLabel(layout->latexname(),
1111 numbertype, langtype, head);
1113 par->params().labelString(STRCONV(s.str()));
1115 // reset enum counters
1116 textclass.counters().reset("enum");
1117 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1118 textclass.counters().reset("enum");
1119 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1121 // Yes I know this is a really, really! bad solution
1123 string enumcounter("enum");
1125 switch (par->enumdepth) {
1134 enumcounter += "iv";
1137 // not a valid enumdepth...
1141 textclass.counters().step(enumcounter);
1143 s << textclass.counters()
1144 .numberLabel(enumcounter, "enumeration");
1145 par->params().labelString(STRCONV(s.str()));
1147 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1148 textclass.counters().step("bibitem");
1149 int number = textclass.counters().value("bibitem");
1150 if (par->bibitem()) {
1151 par->bibitem()->setCounter(number);
1152 par->params().labelString(layout->labelstring());
1154 // In biblio should't be following counters but...
1156 string s = layout->labelstring();
1158 // the caption hack:
1159 if (layout->labeltype == LABEL_SENSITIVE) {
1160 Paragraph * tmppar = par;
1163 while (tmppar && tmppar->inInset()
1164 // the single '=' is intended below
1165 && (in = tmppar->inInset()->owner())) {
1166 if (in->lyxCode() == Inset::FLOAT_CODE ||
1167 in->lyxCode() == Inset::WRAP_CODE) {
1171 tmppar = in->parOwner();
1177 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1179 textclass.counters().step(fl.type());
1181 // Doesn't work... yet.
1182 #if USE_BOOST_FORMAT
1183 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1184 // s << boost::format(_("%1$s %1$d:")
1186 // % buf->counters().value(fl.name());
1189 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1190 o << fl.name() << " #:";
1191 s = STRCONV(o.str());
1194 // par->SetLayout(0);
1195 // s = layout->labelstring;
1196 s = _("Senseless: ");
1199 par->params().labelString(s);
1201 // reset the enumeration counter. They are always reset
1202 // when there is any other layout between
1203 // Just fall-through between the cases so that all
1204 // enum counters deeper than enumdepth is also reset.
1205 switch (par->enumdepth) {
1207 textclass.counters().reset("enumi");
1209 textclass.counters().reset("enumii");
1211 textclass.counters().reset("enumiii");
1213 textclass.counters().reset("enumiv");
1219 // Updates all counters. Paragraphs with changed label string will be rebroken
1220 void LyXText::updateCounters()
1222 RowList::iterator rowit = rows().begin();
1223 ParagraphList::iterator pit = rowit->par();
1225 // CHECK if this is really needed. (Lgb)
1226 bv()->buffer()->params.getLyXTextClass().counters().reset();
1228 while (pit != ownerParagraphs().end()) {
1229 while (rowit->par() != pit)
1232 string const oldLabel = pit->params().labelString();
1234 // setCounter can potentially change the labelString.
1235 setCounter(bv()->buffer(), &*pit);
1237 string const & newLabel = pit->params().labelString();
1239 if (oldLabel.empty() && !newLabel.empty()) {
1240 removeParagraph(rowit);
1241 appendParagraph(rowit);
1249 void LyXText::insertInset(Inset * inset)
1251 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1253 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1255 cursor.par()->insertInset(cursor.pos(), inset);
1256 // Just to rebreak and refresh correctly.
1257 // The character will not be inserted a second time
1258 insertChar(Paragraph::META_INSET);
1259 // If we enter a highly editable inset the cursor should be to before
1260 // the inset. This couldn't happen before as Undo was not handled inside
1261 // inset now after the Undo LyX tries to call inset->Edit(...) again
1262 // and cannot do this as the cursor is behind the inset and GetInset
1263 // does not return the inset!
1264 if (isHighlyEditableInset(inset)) {
1271 void LyXText::copyEnvironmentType()
1273 copylayouttype = cursor.par()->layout()->name();
1277 void LyXText::pasteEnvironmentType()
1279 // do nothing if there has been no previous copyEnvironmentType()
1280 if (!copylayouttype.empty())
1281 setLayout(copylayouttype);
1285 void LyXText::cutSelection(bool doclear, bool realcut)
1287 // Stuff what we got on the clipboard. Even if there is no selection.
1289 // There is a problem with having the stuffing here in that the
1290 // larger the selection the slower LyX will get. This can be
1291 // solved by running the line below only when the selection has
1292 // finished. The solution used currently just works, to make it
1293 // faster we need to be more clever and probably also have more
1294 // calls to stuffClipboard. (Lgb)
1295 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1297 // This doesn't make sense, if there is no selection
1298 if (!selection.set())
1301 // OK, we have a selection. This is always between selection.start
1302 // and selection.end
1304 // make sure that the depth behind the selection are restored, too
1305 Paragraph * endpar = selection.end.par()->next();
1306 Paragraph * undoendpar = endpar;
1308 if (endpar && endpar->getDepth()) {
1309 while (endpar && endpar->getDepth()) {
1310 endpar = endpar->next();
1311 undoendpar = endpar;
1313 } else if (endpar) {
1314 endpar = endpar->next(); // because of parindents etc.
1317 setUndo(bv(), Undo::DELETE,
1318 selection.start.par(), undoendpar);
1320 // there are two cases: cut only within one paragraph or
1321 // more than one paragraph
1322 if (selection.start.par() == selection.end.par()) {
1323 // only within one paragraph
1324 endpar = selection.end.par();
1325 int pos = selection.end.pos();
1326 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1327 selection.start.pos(), pos,
1328 bv()->buffer()->params.textclass,
1330 selection.end.pos(pos);
1332 endpar = selection.end.par();
1333 int pos = selection.end.pos();
1334 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1335 selection.start.pos(), pos,
1336 bv()->buffer()->params.textclass,
1339 selection.end.par(endpar);
1340 selection.end.pos(pos);
1341 cursor.pos(selection.end.pos());
1343 endpar = endpar->next();
1345 // sometimes necessary
1347 selection.start.par()->stripLeadingSpaces();
1349 redoParagraphs(selection.start, endpar);
1351 // cutSelection can invalidate the cursor so we need to set
1353 // we prefer the end for when tracking changes
1354 cursor = selection.end;
1356 // need a valid cursor. (Lgb)
1359 setCursor(cursor.par(), cursor.pos());
1360 selection.cursor = cursor;
1365 void LyXText::copySelection()
1367 // stuff the selection onto the X clipboard, from an explicit copy request
1368 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1370 // this doesnt make sense, if there is no selection
1371 if (!selection.set())
1374 // ok we have a selection. This is always between selection.start
1375 // and sel_end cursor
1377 // copy behind a space if there is one
1378 while (selection.start.par()->size() > selection.start.pos()
1379 && selection.start.par()->isLineSeparator(selection.start.pos())
1380 && (selection.start.par() != selection.end.par()
1381 || selection.start.pos() < selection.end.pos()))
1382 selection.start.pos(selection.start.pos() + 1);
1384 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1385 selection.start.pos(), selection.end.pos(),
1386 bv()->buffer()->params.textclass);
1390 void LyXText::pasteSelection()
1392 // this does not make sense, if there is nothing to paste
1393 if (!CutAndPaste::checkPastePossible())
1396 setUndo(bv(), Undo::INSERT,
1397 cursor.par(), cursor.par()->next());
1400 Paragraph * actpar = cursor.par();
1401 int pos = cursor.pos();
1403 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1404 bv()->buffer()->params.textclass);
1406 redoParagraphs(cursor, endpar);
1408 setCursor(cursor.par(), cursor.pos());
1411 selection.cursor = cursor;
1412 setCursor(actpar, pos);
1418 void LyXText::setSelectionRange(lyx::pos_type length)
1423 selection.cursor = cursor;
1430 // simple replacing. The font of the first selected character is used
1431 void LyXText::replaceSelectionWithString(string const & str)
1433 setCursorParUndo(bv());
1436 if (!selection.set()) { // create a dummy selection
1437 selection.end = cursor;
1438 selection.start = cursor;
1441 // Get font setting before we cut
1442 pos_type pos = selection.end.pos();
1443 LyXFont const font = selection.start.par()
1444 ->getFontSettings(bv()->buffer()->params,
1445 selection.start.pos());
1447 // Insert the new string
1448 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1449 selection.end.par()->insertChar(pos, (*cit), font);
1453 // Cut the selection
1454 cutSelection(true, false);
1460 // needed to insert the selection
1461 void LyXText::insertStringAsLines(string const & str)
1463 Paragraph * par = cursor.par();
1464 pos_type pos = cursor.pos();
1465 Paragraph * endpar = cursor.par()->next();
1467 setCursorParUndo(bv());
1469 // only to be sure, should not be neccessary
1472 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1474 redoParagraphs(cursor, endpar);
1475 setCursor(cursor.par(), cursor.pos());
1476 selection.cursor = cursor;
1477 setCursor(par, pos);
1482 // turns double-CR to single CR, others where converted into one
1483 // blank. Then InsertStringAsLines is called
1484 void LyXText::insertStringAsParagraphs(string const & str)
1486 string linestr(str);
1487 bool newline_inserted = false;
1488 for (string::size_type i = 0; i < linestr.length(); ++i) {
1489 if (linestr[i] == '\n') {
1490 if (newline_inserted) {
1491 // we know that \r will be ignored by
1492 // InsertStringA. Of course, it is a dirty
1493 // trick, but it works...
1494 linestr[i - 1] = '\r';
1498 newline_inserted = true;
1500 } else if (IsPrintable(linestr[i])) {
1501 newline_inserted = false;
1504 insertStringAsLines(linestr);
1508 void LyXText::checkParagraph(Paragraph * par, pos_type pos)
1510 LyXCursor tmpcursor;
1514 RowList::iterator row = getRow(par, pos, y);
1515 RowList::iterator beg = rows().begin();
1517 // is there a break one row above
1519 && boost::prior(row)->par() == row->par()) {
1520 z = rowBreakPoint(*boost::prior(row));
1521 if (z >= row->pos()) {
1522 // set the dimensions of the row above
1523 y -= boost::prior(row)->height();
1526 breakAgain(boost::prior(row));
1528 // set the cursor again. Otherwise
1529 // dangling pointers are possible
1530 setCursor(cursor.par(), cursor.pos(),
1531 false, cursor.boundary());
1532 selection.cursor = cursor;
1537 int const tmpheight = row->height();
1538 pos_type const tmplast = lastPos(*this, row);
1541 if (row->height() == tmpheight && lastPos(*this, row) == tmplast) {
1542 postRowPaint(row, y);
1547 // check the special right address boxes
1548 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1555 redoDrawingOfParagraph(tmpcursor);
1558 // set the cursor again. Otherwise dangling pointers are possible
1559 // also set the selection
1561 if (selection.set()) {
1563 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1564 false, selection.cursor.boundary());
1565 selection.cursor = cursor;
1566 setCursorIntern(selection.start.par(),
1567 selection.start.pos(),
1568 false, selection.start.boundary());
1569 selection.start = cursor;
1570 setCursorIntern(selection.end.par(),
1571 selection.end.pos(),
1572 false, selection.end.boundary());
1573 selection.end = cursor;
1574 setCursorIntern(last_sel_cursor.par(),
1575 last_sel_cursor.pos(),
1576 false, last_sel_cursor.boundary());
1577 last_sel_cursor = cursor;
1580 setCursorIntern(cursor.par(), cursor.pos(),
1581 false, cursor.boundary());
1585 // returns false if inset wasn't found
1586 bool LyXText::updateInset(Inset * inset)
1588 // first check the current paragraph
1589 int pos = cursor.par()->getPositionOfInset(inset);
1591 checkParagraph(cursor.par(), pos);
1595 // check every paragraph
1597 ParagraphList::iterator par = ownerParagraphs().begin();
1598 ParagraphList::iterator end = ownerParagraphs().end();
1601 pos = par->getPositionOfInset(inset);
1603 checkParagraph(&*par, pos);
1607 } while (par != end);
1613 bool LyXText::setCursor(Paragraph * par,
1615 bool setfont, bool boundary)
1617 LyXCursor old_cursor = cursor;
1618 setCursorIntern(par, pos, setfont, boundary);
1619 return deleteEmptyParagraphMechanism(old_cursor);
1623 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1624 pos_type pos, bool boundary)
1630 cur.boundary(boundary);
1632 // get the cursor y position in text
1634 RowList::iterator row = getRow(par, pos, y);
1635 RowList::iterator beg = rows().begin();
1637 RowList::iterator old_row = row;
1639 // if we are before the first char of this row and are still in the
1640 // same paragraph and there is a previous row then put the cursor on
1641 // the end of the previous row
1642 cur.iy(y + row->baseline());
1644 if (row != beg && pos &&
1645 boost::prior(row)->par() == row->par() &&
1646 pos < par->size() &&
1647 par->getChar(pos) == Paragraph::META_INSET &&
1648 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1655 // y is now the beginning of the cursor row
1656 y += row->baseline();
1657 // y is now the cursor baseline
1660 pos_type last = lastPrintablePos(*this, old_row);
1662 // None of these should happen, but we're scaredy-cats
1663 if (pos > par->size()) {
1664 lyxerr << "dont like 1 please report" << endl;
1667 } else if (pos > last + 1) {
1668 lyxerr << "dont like 2 please report" << endl;
1669 // This shouldn't happen.
1672 } else if (pos < row->pos()) {
1673 lyxerr << "dont like 3 please report" << endl;
1678 // now get the cursors x position
1679 float x = getCursorX(row, pos, last, boundary);
1682 if (old_row != row) {
1683 x = getCursorX(old_row, pos, last, boundary);
1687 /* We take out this for the time being because 1) the redraw code is not
1688 prepared to this yet and 2) because some good policy has yet to be decided
1689 while editting: for instance how to act on rows being created/deleted
1693 //if the cursor is in a visible row, anchor to it
1695 if (topy < y && y < topy + bv()->workHeight())
1701 float LyXText::getCursorX(RowList::iterator rit,
1702 pos_type pos, pos_type last, bool boundary) const
1704 pos_type cursor_vpos = 0;
1706 float fill_separator;
1708 float fill_label_hfill;
1709 // This call HAS to be here because of the BidiTables!!!
1710 prepareToPrint(rit, x, fill_separator, fill_hfill,
1713 if (last < rit->pos())
1714 cursor_vpos = rit->pos();
1715 else if (pos > last && !boundary)
1716 cursor_vpos = (rit->par()->isRightToLeftPar(bv()->buffer()->params))
1717 ? rit->pos() : last + 1;
1718 else if (pos > rit->pos() &&
1719 (pos > last || boundary))
1720 /// Place cursor after char at (logical) position pos - 1
1721 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1722 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1724 /// Place cursor before char at (logical) position pos
1725 cursor_vpos = (bidi_level(pos) % 2 == 0)
1726 ? log2vis(pos) : log2vis(pos) + 1;
1728 pos_type body_pos = rit->par()->beginningOfBody();
1729 if ((body_pos > 0) &&
1730 ((body_pos - 1 > last) ||
1731 !rit->par()->isLineSeparator(body_pos - 1)))
1734 for (pos_type vpos = rit->pos(); vpos < cursor_vpos; ++vpos) {
1735 pos_type pos = vis2log(vpos);
1736 if (body_pos > 0 && pos == body_pos - 1) {
1737 x += fill_label_hfill +
1738 font_metrics::width(
1739 rit->par()->layout()->labelsep,
1740 getLabelFont(bv()->buffer(),
1742 if (rit->par()->isLineSeparator(body_pos - 1))
1743 x -= singleWidth(&*rit->par(), body_pos - 1);
1746 if (hfillExpansion(*this, rit, pos)) {
1747 x += singleWidth(&*rit->par(), pos);
1748 if (pos >= body_pos)
1751 x += fill_label_hfill;
1752 } else if (rit->par()->isSeparator(pos)) {
1753 x += singleWidth(&*rit->par(), pos);
1754 if (pos >= body_pos)
1755 x += fill_separator;
1757 x += singleWidth(&*rit->par(), pos);
1763 void LyXText::setCursorIntern(Paragraph * par,
1764 pos_type pos, bool setfont, bool boundary)
1766 InsetText * it = static_cast<InsetText *>(par->inInset());
1768 if (it != inset_owner) {
1769 lyxerr[Debug::INSETS] << "InsetText is " << it
1771 << "inset_owner is "
1772 << inset_owner << endl;
1773 #ifdef WITH_WARNINGS
1774 #warning I believe this code is wrong. (Lgb)
1775 #warning Jürgen, have a look at this. (Lgb)
1776 #warning Hmmm, I guess you are right but we
1777 #warning should verify when this is needed
1779 // Jürgen, would you like to have a look?
1780 // I guess we need to move the outer cursor
1781 // and open and lock the inset (bla bla bla)
1782 // stuff I don't know... so can you have a look?
1784 // I moved the lyxerr stuff in here so we can see if
1785 // this is actually really needed and where!
1787 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1792 setCursor(cursor, par, pos, boundary);
1798 void LyXText::setCurrentFont()
1800 pos_type pos = cursor.pos();
1801 if (cursor.boundary() && pos > 0)
1805 if (pos == cursor.par()->size())
1807 else // potentional bug... BUG (Lgb)
1808 if (cursor.par()->isSeparator(pos)) {
1809 if (pos > cursor.row()->pos() &&
1810 bidi_level(pos) % 2 ==
1811 bidi_level(pos - 1) % 2)
1813 else if (pos + 1 < cursor.par()->size())
1819 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1820 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1822 if (cursor.pos() == cursor.par()->size() &&
1823 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1824 !cursor.boundary()) {
1825 Language const * lang =
1826 cursor.par()->getParLanguage(bv()->buffer()->params);
1827 current_font.setLanguage(lang);
1828 current_font.setNumber(LyXFont::OFF);
1829 real_current_font.setLanguage(lang);
1830 real_current_font.setNumber(LyXFont::OFF);
1835 // returns the column near the specified x-coordinate of the row
1836 // x is set to the real beginning of this column
1838 LyXText::getColumnNearX(RowList::iterator rit, int & x, bool & boundary) const
1841 float fill_separator;
1843 float fill_label_hfill;
1845 prepareToPrint(rit, tmpx, fill_separator,
1846 fill_hfill, fill_label_hfill);
1848 pos_type vc = rit->pos();
1849 pos_type last = lastPrintablePos(*this, rit);
1852 LyXLayout_ptr const & layout = rit->par()->layout();
1854 bool left_side = false;
1856 pos_type body_pos = rit->par()->beginningOfBody();
1857 float last_tmpx = tmpx;
1860 (body_pos - 1 > last ||
1861 !rit->par()->isLineSeparator(body_pos - 1)))
1864 // check for empty row
1865 if (!rit->par()->size()) {
1870 while (vc <= last && tmpx <= x) {
1873 if (body_pos > 0 && c == body_pos - 1) {
1874 tmpx += fill_label_hfill +
1875 font_metrics::width(layout->labelsep,
1876 getLabelFont(bv()->buffer(), &*rit->par()));
1877 if (rit->par()->isLineSeparator(body_pos - 1))
1878 tmpx -= singleWidth(&*rit->par(), body_pos - 1);
1881 if (hfillExpansion(*this, rit, c)) {
1882 tmpx += singleWidth(&*rit->par(), c);
1886 tmpx += fill_label_hfill;
1887 } else if (rit->par()->isSeparator(c)) {
1888 tmpx += singleWidth(&*rit->par(), c);
1890 tmpx+= fill_separator;
1892 tmpx += singleWidth(&*rit->par(), c);
1897 if ((tmpx + last_tmpx) / 2 > x) {
1902 if (vc > last + 1) // This shouldn't happen.
1906 // This (rtl_support test) is not needed, but gives
1907 // some speedup if rtl_support=false
1908 bool const lastrow = lyxrc.rtl_support &&
1909 (boost::next(rit) == rowlist_.end() ||
1910 boost::next(rit)->par() != rit->par());
1911 // If lastrow is false, we don't need to compute
1912 // the value of rtl.
1913 bool const rtl = (lastrow)
1914 ? rit->par()->isRightToLeftPar(bv()->buffer()->params)
1917 ((rtl && left_side && vc == rit->pos() && x < tmpx - 5) ||
1918 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1920 else if (vc == rit->pos()) {
1922 if (bidi_level(c) % 2 == 1)
1925 c = vis2log(vc - 1);
1926 bool const rtl = (bidi_level(c) % 2 == 1);
1927 if (left_side == rtl) {
1929 boundary = isBoundary(bv()->buffer(), &*rit->par(), c);
1933 if (rit->pos() <= last && c > last
1934 && rit->par()->isNewline(last)) {
1935 if (bidi_level(last) % 2 == 0)
1936 tmpx -= singleWidth(&*rit->par(), last);
1938 tmpx += singleWidth(&*rit->par(), last);
1948 void LyXText::setCursorFromCoordinates(int x, int y)
1950 LyXCursor old_cursor = cursor;
1952 setCursorFromCoordinates(cursor, x, y);
1954 deleteEmptyParagraphMechanism(old_cursor);
1961 * return true if the cursor given is at the end of a row,
1962 * and the next row is filled by an inset that spans an entire
1965 bool beforeFullRowInset(LyXText & lt, RowList::iterator row,
1967 if (boost::next(row) == lt.rows().end())
1969 Row const & next = *boost::next(row);
1971 if (next.pos() != cur.pos() || next.par() != cur.par())
1973 if (!cur.par()->isInset(cur.pos()))
1975 Inset const * inset = cur.par()->getInset(cur.pos());
1976 if (inset->needFullRow() || inset->display())
1983 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1985 // Get the row first.
1987 RowList::iterator row = getRowNearY(y);
1989 pos_type const column = getColumnNearX(row, x, bound);
1990 cur.par(&*row->par());
1991 cur.pos(row->pos() + column);
1993 cur.y(y + row->baseline());
1996 if (beforeFullRowInset(*this, row, cur)) {
1997 pos_type last = lastPrintablePos(*this, row);
1998 float x = getCursorX(boost::next(row), cur.pos(), last, bound);
2000 cur.iy(y + row->height() + boost::next(row)->baseline());
2001 cur.irow(boost::next(row));
2007 cur.boundary(bound);
2011 void LyXText::cursorLeft(bool internal)
2013 if (cursor.pos() > 0) {
2014 bool boundary = cursor.boundary();
2015 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2016 if (!internal && !boundary &&
2017 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2018 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2019 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2020 Paragraph * par = cursor.par()->previous();
2021 setCursor(par, par->size());
2026 void LyXText::cursorRight(bool internal)
2028 if (!internal && cursor.boundary() &&
2029 !cursor.par()->isNewline(cursor.pos()))
2030 setCursor(cursor.par(), cursor.pos(), true, false);
2031 else if (cursor.pos() < cursor.par()->size()) {
2032 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2034 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2035 setCursor(cursor.par(), cursor.pos(), true, true);
2036 } else if (cursor.par()->next())
2037 setCursor(cursor.par()->next(), 0);
2041 void LyXText::cursorUp(bool selecting)
2044 int x = cursor.x_fix();
2045 int y = cursor.y() - cursor.row()->baseline() - 1;
2046 setCursorFromCoordinates(x, y);
2049 int y1 = cursor.iy() - topy;
2052 Inset * inset_hit = checkInsetHit(x, y1);
2053 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2054 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2058 setCursorFromCoordinates(bv(), cursor.x_fix(),
2059 cursor.y() - cursor.row()->baseline() - 1);
2064 void LyXText::cursorDown(bool selecting)
2067 int x = cursor.x_fix();
2068 int y = cursor.y() - cursor.row()->baseline() +
2069 cursor.row()->height() + 1;
2070 setCursorFromCoordinates(x, y);
2071 if (!selecting && cursor.row() == cursor.irow()) {
2073 int y1 = cursor.iy() - topy;
2076 Inset * inset_hit = checkInsetHit(x, y1);
2077 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2078 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2082 setCursorFromCoordinates(bv(), cursor.x_fix(),
2083 cursor.y() - cursor.row()->baseline()
2084 + cursor.row()->height() + 1);
2089 void LyXText::cursorUpParagraph()
2091 if (cursor.pos() > 0) {
2092 setCursor(cursor.par(), 0);
2094 else if (cursor.par()->previous()) {
2095 setCursor(cursor.par()->previous(), 0);
2100 void LyXText::cursorDownParagraph()
2102 if (cursor.par()->next()) {
2103 setCursor(cursor.par()->next(), 0);
2105 setCursor(cursor.par(), cursor.par()->size());
2109 // fix the cursor `cur' after a characters has been deleted at `where'
2110 // position. Called by deleteEmptyParagraphMechanism
2111 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2112 LyXCursor const & where)
2114 // if cursor is not in the paragraph where the delete occured,
2116 if (cur.par() != where.par())
2119 // if cursor position is after the place where the delete occured,
2121 if (cur.pos() > where.pos())
2122 cur.pos(cur.pos()-1);
2124 // check also if we don't want to set the cursor on a spot behind the
2125 // pagragraph because we erased the last character.
2126 if (cur.pos() > cur.par()->size())
2127 cur.pos(cur.par()->size());
2129 // recompute row et al. for this cursor
2130 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2134 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2136 // Would be wrong to delete anything if we have a selection.
2137 if (selection.set())
2140 // We allow all kinds of "mumbo-jumbo" when freespacing.
2141 if (old_cursor.par()->layout()->free_spacing
2142 || old_cursor.par()->isFreeSpacing()) {
2146 /* Ok I'll put some comments here about what is missing.
2147 I have fixed BackSpace (and thus Delete) to not delete
2148 double-spaces automagically. I have also changed Cut,
2149 Copy and Paste to hopefully do some sensible things.
2150 There are still some small problems that can lead to
2151 double spaces stored in the document file or space at
2152 the beginning of paragraphs. This happens if you have
2153 the cursor betwenn to spaces and then save. Or if you
2154 cut and paste and the selection have a space at the
2155 beginning and then save right after the paste. I am
2156 sure none of these are very hard to fix, but I will
2157 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2158 that I can get some feedback. (Lgb)
2161 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2162 // delete the LineSeparator.
2165 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2166 // delete the LineSeparator.
2169 // If the pos around the old_cursor were spaces, delete one of them.
2170 if (old_cursor.par() != cursor.par()
2171 || old_cursor.pos() != cursor.pos()) {
2172 // Only if the cursor has really moved
2174 if (old_cursor.pos() > 0
2175 && old_cursor.pos() < old_cursor.par()->size()
2176 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2177 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2178 old_cursor.par()->erase(old_cursor.pos() - 1);
2179 redoParagraphs(old_cursor, old_cursor.par()->next());
2181 #ifdef WITH_WARNINGS
2182 #warning This will not work anymore when we have multiple views of the same buffer
2183 // In this case, we will have to correct also the cursors held by
2184 // other bufferviews. It will probably be easier to do that in a more
2185 // automated way in LyXCursor code. (JMarc 26/09/2001)
2187 // correct all cursors held by the LyXText
2188 fixCursorAfterDelete(cursor, old_cursor);
2189 fixCursorAfterDelete(selection.cursor,
2191 fixCursorAfterDelete(selection.start,
2193 fixCursorAfterDelete(selection.end, old_cursor);
2194 fixCursorAfterDelete(last_sel_cursor,
2196 fixCursorAfterDelete(toggle_cursor, old_cursor);
2197 fixCursorAfterDelete(toggle_end_cursor,
2203 // don't delete anything if this is the ONLY paragraph!
2204 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2207 // Do not delete empty paragraphs with keepempty set.
2208 if (old_cursor.par()->layout()->keepempty)
2211 // only do our magic if we changed paragraph
2212 if (old_cursor.par() == cursor.par())
2215 // record if we have deleted a paragraph
2216 // we can't possibly have deleted a paragraph before this point
2217 bool deleted = false;
2219 if ((old_cursor.par()->empty()
2220 || (old_cursor.par()->size() == 1
2221 && old_cursor.par()->isLineSeparator(0)))) {
2222 // ok, we will delete anything
2223 LyXCursor tmpcursor;
2227 if (old_cursor.row() != rows().begin()) {
2228 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2229 - boost::prior(old_cursor.row())->height());
2231 cursor = old_cursor; // that undo can restore the right cursor position
2232 Paragraph * endpar = old_cursor.par()->next();
2233 if (endpar && endpar->getDepth()) {
2234 while (endpar && endpar->getDepth()) {
2235 endpar = endpar->next();
2238 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2242 removeRow(old_cursor.row());
2243 if (ownerParagraphs().begin() == old_cursor.par()) {
2244 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2247 delete old_cursor.par();
2249 /* Breakagain the next par. Needed because of
2250 * the parindent that can occur or dissappear.
2251 * The next row can change its height, if
2252 * there is another layout before */
2253 if (refresh_row != rows().end()) {
2254 if (boost::next(refresh_row) != rows().end()) {
2255 breakAgain(boost::next(refresh_row));
2258 setHeightOfRow(refresh_row);
2261 RowList::iterator nextrow = boost::next(old_cursor.row());
2262 const_cast<LyXText *>(this)->postPaint(
2263 old_cursor.y() - old_cursor.row()->baseline());
2266 cursor = old_cursor; // that undo can restore the right cursor position
2267 Paragraph * endpar = old_cursor.par()->next();
2268 if (endpar && endpar->getDepth()) {
2269 while (endpar && endpar->getDepth()) {
2270 endpar = endpar->next();
2273 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2277 removeRow(old_cursor.row());
2279 if (ownerParagraphs().begin() == old_cursor.par()) {
2280 ownerParagraph(&*boost::next(ownerParagraphs().begin()));
2283 delete old_cursor.par();
2285 /* Breakagain the next par. Needed because of
2286 the parindent that can occur or dissappear.
2287 The next row can change its height, if
2288 there is another layout before */
2289 if (nextrow != rows().end()) {
2290 breakAgain(nextrow);
2296 setCursorIntern(cursor.par(), cursor.pos());
2298 if (selection.cursor.par() == old_cursor.par()
2299 && selection.cursor.pos() == old_cursor.pos()) {
2300 // correct selection
2301 selection.cursor = cursor;
2305 if (old_cursor.par()->stripLeadingSpaces()) {
2306 redoParagraphs(old_cursor,
2307 old_cursor.par()->next());
2309 setCursorIntern(cursor.par(), cursor.pos());
2310 selection.cursor = cursor;
2317 ParagraphList & LyXText::ownerParagraphs() const
2320 return inset_owner->paragraphs;
2322 return bv_owner->buffer()->paragraphs;
2326 void LyXText::ownerParagraph(Paragraph * p) const
2329 inset_owner->paragraph(p);
2331 bv_owner->buffer()->paragraphs.set(p);
2336 void LyXText::ownerParagraph(int id, Paragraph * p) const
2338 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2339 if (op && op->inInset()) {
2340 static_cast<InsetText *>(op->inInset())->paragraph(p);
2347 LyXText::refresh_status LyXText::refreshStatus() const
2349 return refresh_status_;
2353 void LyXText::clearPaint()
2355 refresh_status_ = REFRESH_NONE;
2356 refresh_row = rows().end();
2361 void LyXText::postPaint(int start_y)
2363 refresh_status old = refresh_status_;
2365 refresh_status_ = REFRESH_AREA;
2366 refresh_row = rows().end();
2368 if (old != REFRESH_NONE && refresh_y < start_y)
2371 refresh_y = start_y;
2376 // We are an inset's lyxtext. Tell the top-level lyxtext
2377 // it needs to update the row we're in.
2378 LyXText * t = bv()->text;
2379 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2383 // FIXME: we should probably remove this y parameter,
2384 // make refresh_y be 0, and use row->y etc.
2385 void LyXText::postRowPaint(RowList::iterator rit, int start_y)
2387 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2388 refresh_status_ = REFRESH_AREA;
2391 refresh_y = start_y;
2394 if (refresh_status_ == REFRESH_AREA)
2397 refresh_status_ = REFRESH_ROW;
2403 // We are an inset's lyxtext. Tell the top-level lyxtext
2404 // it needs to update the row we're in.
2405 LyXText * t = bv()->text;
2406 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2410 bool LyXText::isInInset() const
2412 // Sub-level has non-null bv owner and
2413 // non-null inset owner.
2414 return inset_owner != 0 && bv_owner != 0;
2418 int defaultRowHeight()
2420 LyXFont const font(LyXFont::ALL_SANE);
2421 return int(font_metrics::maxAscent(font)
2422 + font_metrics::maxDescent(font) * 1.5);