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 "BoostFormat.h"
54 LyXText::LyXText(BufferView * bv)
55 : height(0), width(0), anchor_row_(0), anchor_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), need_break_row(0),
64 LyXText::LyXText(BufferView * bv, InsetText * inset)
65 : height(0), width(0), anchor_row_(0), anchor_row_offset_(0),
66 inset_owner(inset), the_locking_inset(0), need_break_row(0),
74 void LyXText::init(BufferView * bview, bool reinit)
80 copylayouttype.erase();
83 } else if (firstRow())
86 Paragraph * par = ownerParagraph();
87 current_font = getFont(bview->buffer(), par, 0);
91 insertParagraph(par, 0);
93 insertParagraph(par, lastRow());
96 setCursorIntern(firstRow()->par(), 0);
97 selection.cursor = cursor;
105 LyXFont const realizeFont(LyXFont const & font,
109 LyXTextClass const & tclass = buf->params.getLyXTextClass();
110 LyXFont tmpfont(font);
111 Paragraph::depth_type par_depth = par->getDepth();
113 // Resolve against environment font information
114 while (par && par_depth && !tmpfont.resolved()) {
115 par = par->outerHook();
117 tmpfont.realize(par->layout()->font);
118 par_depth = par->getDepth();
122 tmpfont.realize(tclass.defaultfont());
130 // Gets the fully instantiated font at a given position in a paragraph
131 // Basically the same routine as Paragraph::getFont() in paragraph.C.
132 // The difference is that this one is used for displaying, and thus we
133 // are allowed to make cosmetic improvements. For instance make footnotes
135 // If position is -1, we get the layout font of the paragraph.
136 // If position is -2, we get the font of the manual label of the paragraph.
137 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
140 lyx::Assert(pos >= 0);
142 LyXLayout_ptr const & layout = par->layout();
144 // We specialize the 95% common case:
145 if (!par->getDepth()) {
146 if (layout->labeltype == LABEL_MANUAL
147 && pos < par->beginningOfBody()) {
149 LyXFont f = par->getFontSettings(buf->params, pos);
151 par->inInset()->getDrawFont(f);
152 return f.realize(layout->reslabelfont);
154 LyXFont f = par->getFontSettings(buf->params, pos);
156 par->inInset()->getDrawFont(f);
157 return f.realize(layout->resfont);
161 // The uncommon case need not be optimized as much
165 if (pos < par->beginningOfBody()) {
167 layoutfont = layout->labelfont;
170 layoutfont = layout->font;
173 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
174 tmpfont.realize(layoutfont);
177 par->inInset()->getDrawFont(tmpfont);
179 return realizeFont(tmpfont, buf, par);
183 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
185 LyXLayout_ptr const & layout = par->layout();
187 if (!par->getDepth()) {
188 return layout->resfont;
191 return realizeFont(layout->font, buf, par);
195 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
197 LyXLayout_ptr const & layout = par->layout();
199 if (!par->getDepth()) {
200 return layout->reslabelfont;
203 return realizeFont(layout->labelfont, buf, par);
207 void LyXText::setCharFont(Paragraph * par,
208 pos_type pos, LyXFont const & fnt,
211 Buffer const * buf = bv()->buffer();
212 LyXFont font = getFont(buf, par, pos);
213 font.update(fnt, buf->params.language, toggleall);
214 // Let the insets convert their font
215 if (par->isInset(pos)) {
216 Inset * inset = par->getInset(pos);
217 if (isEditableInset(inset)) {
218 UpdatableInset * uinset =
219 static_cast<UpdatableInset *>(inset);
220 uinset->setFont(bv(), fnt, toggleall, true);
224 // Plug thru to version below:
225 setCharFont(buf, par, pos, font);
229 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
230 pos_type pos, LyXFont const & fnt)
234 LyXTextClass const & tclass = buf->params.getLyXTextClass();
235 LyXLayout_ptr const & layout = par->layout();
237 // Get concrete layout font to reduce against
240 if (pos < par->beginningOfBody())
241 layoutfont = layout->labelfont;
243 layoutfont = layout->font;
245 // Realize against environment font information
246 if (par->getDepth()) {
247 Paragraph * tp = par;
248 while (!layoutfont.resolved() && tp && tp->getDepth()) {
249 tp = tp->outerHook();
251 layoutfont.realize(tp->layout()->font);
255 layoutfont.realize(tclass.defaultfont());
257 // Now, reduce font against full layout font
258 font.reduce(layoutfont);
260 par->setFont(pos, font);
264 // inserts a new row before the specified row, increments
265 // the touched counters
266 void LyXText::insertRow(Row * row, Paragraph * par,
269 Row * tmprow = new Row;
274 rowlist_.insert(rowlist_.begin(), tmprow);
276 rowlist_.insert(row->next(), tmprow);
281 // removes the row and reset the touched counters
282 void LyXText::removeRow(Row * row)
286 Row * row_prev = row->previous();
287 Row * row_next = row->next();
288 int const row_height = row->height();
290 /* FIXME: when we cache the bview, this should just
291 * become a postPaint(), I think */
292 if (refresh_row == row) {
293 refresh_row = row_prev ? row_prev : row_next;
294 // what about refresh_y
297 if (anchor_row_ == row) {
299 anchor_row_ = row_prev;
300 anchor_row_offset_ += row_prev->height();
302 anchor_row_ = row_next;
303 anchor_row_offset_ -= row_height;
307 // the text becomes smaller
308 height -= row_height;
314 // remove all following rows of the paragraph of the specified row.
315 void LyXText::removeParagraph(Row * row)
317 Paragraph * tmppar = row->par();
321 while (row && row->par() == tmppar) {
322 tmprow = row->next();
329 void LyXText::insertParagraph(Paragraph * par, Row * row)
331 // insert a new row, starting at position 0
332 insertRow(row, par, 0);
334 // and now append the whole paragraph before the new row
336 appendParagraph(firstRow());
338 appendParagraph(row->next());
343 Inset * LyXText::getInset() const
345 if (cursor.pos() < cursor.par()->size()
346 && cursor.par()->isInset(cursor.pos())) {
347 return cursor.par()->getInset(cursor.pos());
353 void LyXText::toggleInset()
355 Inset * inset = getInset();
356 // is there an editable inset at cursor position?
357 if (!isEditableInset(inset)) {
358 // No, try to see if we are inside a collapsable inset
359 if (inset_owner && inset_owner->owner()
360 && inset_owner->owner()->isOpen()) {
361 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
362 inset_owner->owner()->close(bv());
363 bv()->getLyXText()->cursorRight(bv());
367 //bv()->owner()->message(inset->editMessage());
369 // do we want to keep this?? (JMarc)
370 if (!isHighlyEditableInset(inset))
371 setCursorParUndo(bv());
373 if (inset->isOpen()) {
379 bv()->updateInset(inset);
383 /* used in setlayout */
384 // Asger is not sure we want to do this...
385 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
388 LyXLayout_ptr const & layout = par.layout();
391 for (pos_type pos = 0; pos < par.size(); ++pos) {
392 if (pos < par.beginningOfBody())
393 layoutfont = layout->labelfont;
395 layoutfont = layout->font;
397 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
398 tmpfont.reduce(layoutfont);
399 par.setFont(pos, tmpfont);
404 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
405 LyXCursor & send_cur,
406 string const & layout)
408 Paragraph * endpar = send_cur.par()->next();
409 Paragraph * undoendpar = endpar;
411 if (endpar && endpar->getDepth()) {
412 while (endpar && endpar->getDepth()) {
413 endpar = endpar->next();
417 endpar = endpar->next(); // because of parindents etc.
420 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
422 // ok we have a selection. This is always between sstart_cur
423 // and sel_end cursor
425 Paragraph * par = sstart_cur.par();
426 Paragraph * epar = send_cur.par()->next();
428 LyXLayout_ptr const & lyxlayout =
429 bv()->buffer()->params.getLyXTextClass()[layout];
432 par->applyLayout(lyxlayout);
433 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
434 Paragraph * fppar = par;
435 fppar->params().spaceTop(lyxlayout->fill_top ?
436 VSpace(VSpace::VFILL)
437 : VSpace(VSpace::NONE));
438 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
439 VSpace(VSpace::VFILL)
440 : VSpace(VSpace::NONE));
441 if (lyxlayout->margintype == MARGIN_MANUAL)
442 par->setLabelWidthString(lyxlayout->labelstring());
445 } while (par != epar);
451 // set layout over selection and make a total rebreak of those paragraphs
452 void LyXText::setLayout(string const & layout)
454 LyXCursor tmpcursor = cursor; /* store the current cursor */
456 // if there is no selection just set the layout
457 // of the current paragraph */
458 if (!selection.set()) {
459 selection.start = cursor; // dummy selection
460 selection.end = cursor;
462 Paragraph * endpar = setLayout(cursor, selection.start,
463 selection.end, layout);
464 redoParagraphs(selection.start, endpar);
466 // we have to reset the selection, because the
467 // geometry could have changed
468 setCursor(selection.start.par(),
469 selection.start.pos(), false);
470 selection.cursor = cursor;
471 setCursor(selection.end.par(), selection.end.pos(), false);
475 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
479 // increment depth over selection and
480 // make a total rebreak of those paragraphs
481 void LyXText::incDepth()
483 // If there is no selection, just use the current paragraph
484 if (!selection.set()) {
485 selection.start = cursor; // dummy selection
486 selection.end = cursor;
489 // We end at the next paragraph with depth 0
490 Paragraph * endpar = selection.end.par()->next();
492 Paragraph * undoendpar = endpar;
494 if (endpar && endpar->getDepth()) {
495 while (endpar && endpar->getDepth()) {
496 endpar = endpar->next();
500 endpar = endpar->next(); // because of parindents etc.
503 setUndo(bv(), Undo::EDIT,
504 selection.start.par(), undoendpar);
506 LyXCursor tmpcursor = cursor; // store the current cursor
508 // ok we have a selection. This is always between sel_start_cursor
509 // and sel_end cursor
510 cursor = selection.start;
513 // NOTE: you can't change the depth of a bibliography entry
514 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
515 Paragraph * prev = cursor.par()->previous();
518 if (cursor.par()->getDepth()
519 < prev->getMaxDepthAfter()) {
520 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
524 if (cursor.par() == selection.end.par())
526 cursor.par(cursor.par()->next());
529 redoParagraphs(selection.start, endpar);
531 // we have to reset the selection, because the
532 // geometry could have changed
533 setCursor(selection.start.par(), selection.start.pos());
534 selection.cursor = cursor;
535 setCursor(selection.end.par(), selection.end.pos());
539 setCursor(tmpcursor.par(), tmpcursor.pos());
543 // decrement depth over selection and
544 // make a total rebreak of those paragraphs
545 void LyXText::decDepth()
547 // if there is no selection just set the layout
548 // of the current paragraph
549 if (!selection.set()) {
550 selection.start = cursor; // dummy selection
551 selection.end = cursor;
553 Paragraph * endpar = selection.end.par()->next();
554 Paragraph * undoendpar = endpar;
556 if (endpar && endpar->getDepth()) {
557 while (endpar && endpar->getDepth()) {
558 endpar = endpar->next();
562 endpar = endpar->next(); // because of parindents etc.
565 setUndo(bv(), Undo::EDIT,
566 selection.start.par(), undoendpar);
568 LyXCursor tmpcursor = cursor; // store the current cursor
570 // ok we have a selection. This is always between sel_start_cursor
571 // and sel_end cursor
572 cursor = selection.start;
575 if (cursor.par()->params().depth()) {
576 cursor.par()->params()
577 .depth(cursor.par()->params().depth() - 1);
579 if (cursor.par() == selection.end.par()) {
582 cursor.par(cursor.par()->next());
585 redoParagraphs(selection.start, endpar);
587 // we have to reset the selection, because the
588 // geometry could have changed
589 setCursor(selection.start.par(),
590 selection.start.pos());
591 selection.cursor = cursor;
592 setCursor(selection.end.par(), selection.end.pos());
596 setCursor(tmpcursor.par(), tmpcursor.pos());
600 // set font over selection and make a total rebreak of those paragraphs
601 void LyXText::setFont(LyXFont const & font, bool toggleall)
603 // if there is no selection just set the current_font
604 if (!selection.set()) {
605 // Determine basis font
607 if (cursor.pos() < cursor.par()->beginningOfBody()) {
608 layoutfont = getLabelFont(bv()->buffer(),
611 layoutfont = getLayoutFont(bv()->buffer(),
614 // Update current font
615 real_current_font.update(font,
616 bv()->buffer()->params.language,
619 // Reduce to implicit settings
620 current_font = real_current_font;
621 current_font.reduce(layoutfont);
622 // And resolve it completely
623 real_current_font.realize(layoutfont);
628 LyXCursor tmpcursor = cursor; // store the current cursor
630 // ok we have a selection. This is always between sel_start_cursor
631 // and sel_end cursor
633 setUndo(bv(), Undo::EDIT,
634 selection.start.par(), selection.end.par()->next());
636 cursor = selection.start;
637 while (cursor.par() != selection.end.par() ||
638 cursor.pos() < selection.end.pos())
640 if (cursor.pos() < cursor.par()->size()) {
641 // an open footnote should behave like a closed one
642 setCharFont(cursor.par(), cursor.pos(),
644 cursor.pos(cursor.pos() + 1);
647 cursor.par(cursor.par()->next());
652 redoParagraphs(selection.start, selection.end.par()->next());
654 // we have to reset the selection, because the
655 // geometry could have changed, but we keep
656 // it for user convenience
657 setCursor(selection.start.par(), selection.start.pos());
658 selection.cursor = cursor;
659 setCursor(selection.end.par(), selection.end.pos());
661 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
662 tmpcursor.boundary());
666 void LyXText::redoHeightOfParagraph()
668 Row * tmprow = cursor.row();
669 int y = cursor.y() - tmprow->baseline();
671 setHeightOfRow(tmprow);
673 while (tmprow->previous()
674 && tmprow->previous()->par() == tmprow->par()) {
675 tmprow = tmprow->previous();
676 y -= tmprow->height();
677 setHeightOfRow(tmprow);
682 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
686 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
688 Row * tmprow = cur.row();
690 int y = cur.y() - tmprow->baseline();
691 setHeightOfRow(tmprow);
693 while (tmprow->previous()
694 && tmprow->previous()->par() == tmprow->par()) {
695 tmprow = tmprow->previous();
696 y -= tmprow->height();
700 setCursor(cur.par(), cur.pos());
704 // deletes and inserts again all paragaphs between the cursor
705 // and the specified par
706 // This function is needed after SetLayout and SetFont etc.
707 void LyXText::redoParagraphs(LyXCursor const & cur,
708 Paragraph const * endpar)
710 Row * tmprow = cur.row();
712 int y = cur.y() - tmprow->baseline();
714 Paragraph * first_phys_par = 0;
715 if (!tmprow->previous()) {
716 // a trick/hack for UNDO
717 // This is needed because in an UNDO/REDO we could have changed
718 // the ownerParagrah() so the paragraph inside the row is NOT
719 // my really first par anymore. Got it Lars ;) (Jug 20011206)
720 first_phys_par = ownerParagraph();
721 lyxerr << "ownerParagraph" << endl;
724 first_phys_par = tmprow->par();
725 lyxerr << "tmprow->par()" << endl;
727 // Find first row of this paragraph.
728 while (tmprow->previous()
729 && tmprow->previous()->par() == first_phys_par)
731 tmprow = tmprow->previous();
732 y -= tmprow->height();
736 Row * prevrow = tmprow->previous();
738 // Remove all the rows until we reach endpar
739 Paragraph * tmppar = 0;
741 tmppar = tmprow->next()->par();
742 while (tmprow->next() && tmppar != endpar) {
743 removeRow(tmprow->next());
744 if (tmprow->next()) {
745 tmppar = tmprow->next()->par();
751 // Remove the first of the paragraphs rows.
752 // This is because tmprow->previous() can be 0
753 Row * tmprow2 = tmprow;
754 tmprow = tmprow->previous();
757 // Reinsert the paragraphs.
758 tmppar = first_phys_par;
761 insertParagraph(tmppar, tmprow);
765 while (tmprow->next()
766 && tmprow->next()->par() == tmppar) {
767 tmprow = tmprow->next();
769 tmppar = tmppar->next();
771 } while (tmppar && tmppar != endpar);
773 // this is because of layout changes
775 setHeightOfRow(prevrow);
776 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
778 setHeightOfRow(firstRow());
779 const_cast<LyXText *>(this)->postPaint(0);
782 if (tmprow && tmprow->next())
783 setHeightOfRow(tmprow->next());
788 void LyXText::fullRebreak()
794 if (need_break_row) {
795 breakAgain(need_break_row);
802 // important for the screen
805 // the cursor set functions have a special mechanism. When they
806 // realize, that you left an empty paragraph, they will delete it.
807 // They also delete the corresponding row
809 // need the selection cursor:
810 void LyXText::setSelection()
812 bool const lsel = selection.set();
814 if (!selection.set()) {
815 last_sel_cursor = selection.cursor;
816 selection.start = selection.cursor;
817 selection.end = selection.cursor;
822 // first the toggling area
823 if (cursor.y() < last_sel_cursor.y()
824 || (cursor.y() == last_sel_cursor.y()
825 && cursor.x() < last_sel_cursor.x())) {
826 toggle_end_cursor = last_sel_cursor;
827 toggle_cursor = cursor;
829 toggle_end_cursor = cursor;
830 toggle_cursor = last_sel_cursor;
833 last_sel_cursor = cursor;
835 // and now the whole selection
837 if (selection.cursor.par() == cursor.par())
838 if (selection.cursor.pos() < cursor.pos()) {
839 selection.end = cursor;
840 selection.start = selection.cursor;
842 selection.end = selection.cursor;
843 selection.start = cursor;
845 else if (selection.cursor.y() < cursor.y() ||
846 (selection.cursor.y() == cursor.y()
847 && selection.cursor.x() < cursor.x())) {
848 selection.end = cursor;
849 selection.start = selection.cursor;
852 selection.end = selection.cursor;
853 selection.start = cursor;
856 // a selection with no contents is not a selection
857 if (selection.start.par() == selection.end.par() &&
858 selection.start.pos() == selection.end.pos())
859 selection.set(false);
861 if (inset_owner && (selection.set() || lsel))
862 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
866 string const LyXText::selectionAsString(Buffer const * buffer,
869 if (!selection.set()) return string();
871 // should be const ...
872 Paragraph * startpar(selection.start.par());
873 Paragraph * endpar(selection.end.par());
874 pos_type const startpos(selection.start.pos());
875 pos_type const endpos(selection.end.pos());
877 if (startpar == endpar) {
878 return startpar->asString(buffer, startpos, endpos, label);
883 // First paragraph in selection
884 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
886 // The paragraphs in between (if any)
887 LyXCursor tmpcur(selection.start);
888 tmpcur.par(tmpcur.par()->next());
889 while (tmpcur.par() != endpar) {
890 result += tmpcur.par()->asString(buffer, 0,
891 tmpcur.par()->size(),
893 tmpcur.par(tmpcur.par()->next());
896 // Last paragraph in selection
897 result += endpar->asString(buffer, 0, endpos, label);
903 void LyXText::clearSelection()
905 selection.set(false);
906 selection.mark(false);
907 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
908 // reset this in the bv_owner!
909 if (bv_owner && bv_owner->text)
910 bv_owner->text->xsel_cache.set(false);
914 void LyXText::cursorHome()
916 setCursor(cursor.par(), cursor.row()->pos());
920 void LyXText::cursorEnd()
922 if (cursor.par()->empty())
925 if (!cursor.row()->next()
926 || cursor.row()->next()->par() != cursor.row()->par()) {
927 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
929 if (!cursor.par()->empty() &&
930 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
931 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
932 setCursor(cursor.par(), cursor.row()->lastPos());
934 setCursor(cursor.par(),
935 cursor.row()->lastPos() + 1);
941 void LyXText::cursorTop()
943 while (cursor.par()->previous())
944 cursor.par(cursor.par()->previous());
945 setCursor(cursor.par(), 0);
949 void LyXText::cursorBottom()
951 while (cursor.par()->next())
952 cursor.par(cursor.par()->next());
953 setCursor(cursor.par(), cursor.par()->size());
957 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
959 // If the mask is completely neutral, tell user
960 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
961 // Could only happen with user style
962 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
966 // Try implicit word selection
967 // If there is a change in the language the implicit word selection
969 LyXCursor resetCursor = cursor;
970 bool implicitSelection = (font.language() == ignore_language
971 && font.number() == LyXFont::IGNORE)
972 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
975 setFont(font, toggleall);
977 // Implicit selections are cleared afterwards
978 //and cursor is set to the original position.
979 if (implicitSelection) {
981 cursor = resetCursor;
982 setCursor(cursor.par(), cursor.pos());
983 selection.cursor = cursor;
986 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
990 string LyXText::getStringToIndex()
992 // Try implicit word selection
993 // If there is a change in the language the implicit word selection
995 LyXCursor const reset_cursor = cursor;
996 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
999 if (!selection.set())
1000 bv()->owner()->message(_("Nothing to index!"));
1001 else if (selection.start.par() != selection.end.par())
1002 bv()->owner()->message(_("Cannot index more than one paragraph!"));
1004 idxstring = selectionAsString(bv()->buffer(), false);
1006 // Reset cursors to their original position.
1007 cursor = reset_cursor;
1008 setCursor(cursor.par(), cursor.pos());
1009 selection.cursor = cursor;
1011 // Clear the implicit selection.
1012 if (implicitSelection)
1019 // the DTP switches for paragraphs. LyX will store them in the first
1020 // physicla paragraph. When a paragraph is broken, the top settings rest,
1021 // the bottom settings are given to the new one. So I can make shure,
1022 // they do not duplicate themself and you cannnot make dirty things with
1025 void LyXText::setParagraph(bool line_top, bool line_bottom,
1026 bool pagebreak_top, bool pagebreak_bottom,
1027 VSpace const & space_top,
1028 VSpace const & space_bottom,
1029 Spacing const & spacing,
1031 string const & labelwidthstring,
1034 LyXCursor tmpcursor = cursor;
1035 if (!selection.set()) {
1036 selection.start = cursor;
1037 selection.end = cursor;
1040 // make sure that the depth behind the selection are restored, too
1041 Paragraph * endpar = selection.end.par()->next();
1042 Paragraph * undoendpar = endpar;
1044 if (endpar && endpar->getDepth()) {
1045 while (endpar && endpar->getDepth()) {
1046 endpar = endpar->next();
1047 undoendpar = endpar;
1051 // because of parindents etc.
1052 endpar = endpar->next();
1055 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1058 Paragraph * tmppar = selection.end.par();
1060 while (tmppar != selection.start.par()->previous()) {
1061 setCursor(tmppar, 0);
1062 postPaint(cursor.y() - cursor.row()->baseline());
1063 cursor.par()->params().lineTop(line_top);
1064 cursor.par()->params().lineBottom(line_bottom);
1065 cursor.par()->params().pagebreakTop(pagebreak_top);
1066 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1067 cursor.par()->params().spaceTop(space_top);
1068 cursor.par()->params().spaceBottom(space_bottom);
1069 cursor.par()->params().spacing(spacing);
1070 // does the layout allow the new alignment?
1071 LyXLayout_ptr const & layout = cursor.par()->layout();
1073 if (align == LYX_ALIGN_LAYOUT)
1074 align = layout->align;
1075 if (align & layout->alignpossible) {
1076 if (align == layout->align)
1077 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1079 cursor.par()->params().align(align);
1081 cursor.par()->setLabelWidthString(labelwidthstring);
1082 cursor.par()->params().noindent(noindent);
1083 tmppar = cursor.par()->previous();
1086 redoParagraphs(selection.start, endpar);
1089 setCursor(selection.start.par(), selection.start.pos());
1090 selection.cursor = cursor;
1091 setCursor(selection.end.par(), selection.end.pos());
1093 setCursor(tmpcursor.par(), tmpcursor.pos());
1095 bv()->updateInset(inset_owner);
1099 // set the counter of a paragraph. This includes the labels
1100 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1102 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1103 LyXLayout_ptr const & layout = par->layout();
1105 if (par->previous()) {
1107 par->params().appendix(par->previous()->params().appendix());
1108 if (!par->params().appendix() && par->params().startOfAppendix()) {
1109 par->params().appendix(true);
1110 textclass.counters().reset();
1112 par->enumdepth = par->previous()->enumdepth;
1113 par->itemdepth = par->previous()->itemdepth;
1115 par->params().appendix(par->params().startOfAppendix());
1120 /* Maybe we have to increment the enumeration depth.
1121 * BUT, enumeration in a footnote is considered in isolation from its
1122 * surrounding paragraph so don't increment if this is the
1123 * first line of the footnote
1124 * AND, bibliographies can't have their depth changed ie. they
1125 * are always of depth 0
1128 && par->previous()->getDepth() < par->getDepth()
1129 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1130 && par->enumdepth < 3
1131 && layout->labeltype != LABEL_BIBLIO) {
1135 // Maybe we have to decrement the enumeration depth, see note above
1137 && par->previous()->getDepth() > par->getDepth()
1138 && layout->labeltype != LABEL_BIBLIO) {
1139 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1142 if (!par->params().labelString().empty()) {
1143 par->params().labelString(string());
1146 if (layout->margintype == MARGIN_MANUAL) {
1147 if (par->params().labelWidthString().empty()) {
1148 par->setLabelWidthString(layout->labelstring());
1151 par->setLabelWidthString(string());
1154 // is it a layout that has an automatic label?
1155 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1156 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1160 if (i >= 0 && i <= buf->params.secnumdepth) {
1164 textclass.counters().step(layout->latexname());
1166 // Is there a label? Useful for Chapter layout
1167 if (!par->params().appendix()) {
1168 s << layout->labelstring();
1170 s << layout->labelstring_appendix();
1173 // Use of an integer is here less than elegant. For now.
1174 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1175 if (!par->params().appendix()) {
1176 numbertype = "sectioning";
1178 numbertype = "appendix";
1179 if (par->isRightToLeftPar(buf->params))
1180 langtype = "hebrew";
1185 s << textclass.counters()
1186 .numberLabel(layout->latexname(),
1187 numbertype, langtype, head);
1189 par->params().labelString(STRCONV(s.str()));
1191 // reset enum counters
1192 textclass.counters().reset("enum");
1193 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1194 textclass.counters().reset("enum");
1195 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1197 // Yes I know this is a really, really! bad solution
1199 string enumcounter("enum");
1201 switch (par->enumdepth) {
1210 enumcounter += "iv";
1213 // not a valid enumdepth...
1217 textclass.counters().step(enumcounter);
1219 s << textclass.counters()
1220 .numberLabel(enumcounter, "enumeration");
1221 par->params().labelString(STRCONV(s.str()));
1223 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1224 textclass.counters().step("bibitem");
1225 int number = textclass.counters().value("bibitem");
1226 if (par->bibitem()) {
1227 par->bibitem()->setCounter(number);
1228 par->params().labelString(layout->labelstring());
1230 // In biblio should't be following counters but...
1232 string s = layout->labelstring();
1234 // the caption hack:
1235 if (layout->labeltype == LABEL_SENSITIVE) {
1236 Paragraph * tmppar = par;
1239 while (tmppar && tmppar->inInset()
1240 // the single '=' is intended below
1241 && (in = tmppar->inInset()->owner())) {
1242 if (in->lyxCode() == Inset::FLOAT_CODE ||
1243 in->lyxCode() == Inset::WRAP_CODE) {
1247 tmppar = in->parOwner();
1253 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1255 textclass.counters().step(fl.type());
1257 // Doesn't work... yet.
1258 #warning use boost.format
1259 #if USE_BOOST_FORMAT
1260 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1261 // s << boost::format(_("%1$s %1$d:")
1263 // % buf->counters().value(fl.name());
1266 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1267 o << fl.name() << " #:";
1268 s = STRCONV(o.str());
1271 // par->SetLayout(0);
1272 // s = layout->labelstring;
1273 s = _("Senseless: ");
1276 par->params().labelString(s);
1278 // reset the enumeration counter. They are always reset
1279 // when there is any other layout between
1280 // Just fall-through between the cases so that all
1281 // enum counters deeper than enumdepth is also reset.
1282 switch (par->enumdepth) {
1284 textclass.counters().reset("enumi");
1286 textclass.counters().reset("enumii");
1288 textclass.counters().reset("enumiii");
1290 textclass.counters().reset("enumiv");
1296 // Updates all counters. Paragraphs with changed label string will be rebroken
1297 void LyXText::updateCounters()
1299 Row * row = firstRow();
1300 Paragraph * par = row->par();
1302 // CHECK if this is really needed. (Lgb)
1303 bv()->buffer()->params.getLyXTextClass().counters().reset();
1306 while (row->par() != par)
1309 string const oldLabel = par->params().labelString();
1311 // setCounter can potentially change the labelString.
1312 setCounter(bv()->buffer(), par);
1314 string const & newLabel = par->params().labelString();
1316 if (oldLabel.empty() && !newLabel.empty()) {
1317 removeParagraph(row);
1318 appendParagraph(row);
1326 void LyXText::insertInset(Inset * inset)
1328 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1330 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1332 cursor.par()->insertInset(cursor.pos(), inset);
1333 // Just to rebreak and refresh correctly.
1334 // The character will not be inserted a second time
1335 insertChar(Paragraph::META_INSET);
1336 // If we enter a highly editable inset the cursor should be to before
1337 // the inset. This couldn't happen before as Undo was not handled inside
1338 // inset now after the Undo LyX tries to call inset->Edit(...) again
1339 // and cannot do this as the cursor is behind the inset and GetInset
1340 // does not return the inset!
1341 if (isHighlyEditableInset(inset)) {
1348 void LyXText::copyEnvironmentType()
1350 copylayouttype = cursor.par()->layout()->name();
1354 void LyXText::pasteEnvironmentType()
1356 // do nothing if there has been no previous copyEnvironmentType()
1357 if (!copylayouttype.empty())
1358 setLayout(copylayouttype);
1362 void LyXText::cutSelection(bool doclear, bool realcut)
1364 // Stuff what we got on the clipboard. Even if there is no selection.
1366 // There is a problem with having the stuffing here in that the
1367 // larger the selection the slower LyX will get. This can be
1368 // solved by running the line below only when the selection has
1369 // finished. The solution used currently just works, to make it
1370 // faster we need to be more clever and probably also have more
1371 // calls to stuffClipboard. (Lgb)
1372 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1374 // This doesn't make sense, if there is no selection
1375 if (!selection.set())
1378 // OK, we have a selection. This is always between selection.start
1379 // and selection.end
1381 // make sure that the depth behind the selection are restored, too
1382 Paragraph * endpar = selection.end.par()->next();
1383 Paragraph * undoendpar = endpar;
1385 if (endpar && endpar->getDepth()) {
1386 while (endpar && endpar->getDepth()) {
1387 endpar = endpar->next();
1388 undoendpar = endpar;
1390 } else if (endpar) {
1391 endpar = endpar->next(); // because of parindents etc.
1394 setUndo(bv(), Undo::DELETE,
1395 selection.start.par(), undoendpar);
1397 // there are two cases: cut only within one paragraph or
1398 // more than one paragraph
1399 if (selection.start.par() == selection.end.par()) {
1400 // only within one paragraph
1401 endpar = selection.end.par();
1402 int pos = selection.end.pos();
1403 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1404 selection.start.pos(), pos,
1405 bv()->buffer()->params.textclass,
1407 selection.end.pos(pos);
1409 endpar = selection.end.par();
1410 int pos = selection.end.pos();
1411 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1412 selection.start.pos(), pos,
1413 bv()->buffer()->params.textclass,
1416 selection.end.par(endpar);
1417 selection.end.pos(pos);
1418 cursor.pos(selection.end.pos());
1420 endpar = endpar->next();
1422 // sometimes necessary
1424 selection.start.par()->stripLeadingSpaces();
1426 redoParagraphs(selection.start, endpar);
1428 // cutSelection can invalidate the cursor so we need to set
1430 // we prefer the end for when tracking changes
1431 cursor = selection.end;
1433 // need a valid cursor. (Lgb)
1436 setCursor(cursor.par(), cursor.pos());
1437 selection.cursor = cursor;
1442 void LyXText::copySelection()
1444 // stuff the selection onto the X clipboard, from an explicit copy request
1445 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1447 // this doesnt make sense, if there is no selection
1448 if (!selection.set())
1451 // ok we have a selection. This is always between selection.start
1452 // and sel_end cursor
1454 // copy behind a space if there is one
1455 while (selection.start.par()->size() > selection.start.pos()
1456 && selection.start.par()->isLineSeparator(selection.start.pos())
1457 && (selection.start.par() != selection.end.par()
1458 || selection.start.pos() < selection.end.pos()))
1459 selection.start.pos(selection.start.pos() + 1);
1461 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1462 selection.start.pos(), selection.end.pos(),
1463 bv()->buffer()->params.textclass);
1467 void LyXText::pasteSelection()
1469 // this does not make sense, if there is nothing to paste
1470 if (!CutAndPaste::checkPastePossible())
1473 setUndo(bv(), Undo::INSERT,
1474 cursor.par(), cursor.par()->next());
1477 Paragraph * actpar = cursor.par();
1478 int pos = cursor.pos();
1480 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1481 bv()->buffer()->params.textclass);
1483 redoParagraphs(cursor, endpar);
1485 setCursor(cursor.par(), cursor.pos());
1488 selection.cursor = cursor;
1489 setCursor(actpar, pos);
1495 void LyXText::setSelectionRange(lyx::pos_type length)
1500 selection.cursor = cursor;
1507 // simple replacing. The font of the first selected character is used
1508 void LyXText::replaceSelectionWithString(string const & str)
1510 setCursorParUndo(bv());
1513 if (!selection.set()) { // create a dummy selection
1514 selection.end = cursor;
1515 selection.start = cursor;
1518 // Get font setting before we cut
1519 pos_type pos = selection.end.pos();
1520 LyXFont const font = selection.start.par()
1521 ->getFontSettings(bv()->buffer()->params,
1522 selection.start.pos());
1524 // Insert the new string
1525 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1526 selection.end.par()->insertChar(pos, (*cit), font);
1530 // Cut the selection
1531 cutSelection(true, false);
1537 // needed to insert the selection
1538 void LyXText::insertStringAsLines(string const & str)
1540 Paragraph * par = cursor.par();
1541 pos_type pos = cursor.pos();
1542 Paragraph * endpar = cursor.par()->next();
1544 setCursorParUndo(bv());
1546 // only to be sure, should not be neccessary
1549 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1551 redoParagraphs(cursor, endpar);
1552 setCursor(cursor.par(), cursor.pos());
1553 selection.cursor = cursor;
1554 setCursor(par, pos);
1559 // turns double-CR to single CR, others where converted into one
1560 // blank. Then InsertStringAsLines is called
1561 void LyXText::insertStringAsParagraphs(string const & str)
1563 string linestr(str);
1564 bool newline_inserted = false;
1565 for (string::size_type i = 0; i < linestr.length(); ++i) {
1566 if (linestr[i] == '\n') {
1567 if (newline_inserted) {
1568 // we know that \r will be ignored by
1569 // InsertStringA. Of course, it is a dirty
1570 // trick, but it works...
1571 linestr[i - 1] = '\r';
1575 newline_inserted = true;
1577 } else if (IsPrintable(linestr[i])) {
1578 newline_inserted = false;
1581 insertStringAsLines(linestr);
1585 void LyXText::checkParagraph(Paragraph * par,
1588 LyXCursor tmpcursor;
1592 Row * row = getRow(par, pos, y);
1594 // is there a break one row above
1595 if (row->previous() && row->previous()->par() == row->par()) {
1596 z = rowBreakPoint(*row->previous());
1597 if (z >= row->pos()) {
1598 // set the dimensions of the row above
1599 y -= row->previous()->height();
1602 breakAgain(row->previous());
1604 // set the cursor again. Otherwise
1605 // dangling pointers are possible
1606 setCursor(cursor.par(), cursor.pos(),
1607 false, cursor.boundary());
1608 selection.cursor = cursor;
1613 int const tmpheight = row->height();
1614 pos_type const tmplast = row->lastPos();
1617 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1618 postRowPaint(row, y);
1623 // check the special right address boxes
1624 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1631 redoDrawingOfParagraph(tmpcursor);
1634 // set the cursor again. Otherwise dangling pointers are possible
1635 // also set the selection
1637 if (selection.set()) {
1639 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1640 false, selection.cursor.boundary());
1641 selection.cursor = cursor;
1642 setCursorIntern(selection.start.par(),
1643 selection.start.pos(),
1644 false, selection.start.boundary());
1645 selection.start = cursor;
1646 setCursorIntern(selection.end.par(),
1647 selection.end.pos(),
1648 false, selection.end.boundary());
1649 selection.end = cursor;
1650 setCursorIntern(last_sel_cursor.par(),
1651 last_sel_cursor.pos(),
1652 false, last_sel_cursor.boundary());
1653 last_sel_cursor = cursor;
1656 setCursorIntern(cursor.par(), cursor.pos(),
1657 false, cursor.boundary());
1661 // returns false if inset wasn't found
1662 bool LyXText::updateInset(Inset * inset)
1664 // first check the current paragraph
1665 int pos = cursor.par()->getPositionOfInset(inset);
1667 checkParagraph(cursor.par(), pos);
1671 // check every paragraph
1673 Paragraph * par = ownerParagraph();
1675 pos = par->getPositionOfInset(inset);
1677 checkParagraph(par, pos);
1687 bool LyXText::setCursor(Paragraph * par,
1689 bool setfont, bool boundary)
1691 LyXCursor old_cursor = cursor;
1692 setCursorIntern(par, pos, setfont, boundary);
1693 return deleteEmptyParagraphMechanism(old_cursor);
1697 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1698 pos_type pos, bool boundary)
1704 cur.boundary(boundary);
1706 // get the cursor y position in text
1708 Row * row = getRow(par, pos, y);
1709 Row * old_row = row;
1711 // if we are before the first char of this row and are still in the
1712 // same paragraph and there is a previous row then put the cursor on
1713 // the end of the previous row
1714 cur.iy(y + row->baseline());
1716 if (row->previous() && pos &&
1717 row->previous()->par() == row->par() &&
1718 pos < par->size() &&
1719 par->getChar(pos) == Paragraph::META_INSET &&
1720 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1722 row = row->previous();
1727 // y is now the beginning of the cursor row
1728 y += row->baseline();
1729 // y is now the cursor baseline
1732 pos_type last = old_row->lastPrintablePos();
1734 // None of these should happen, but we're scaredy-cats
1735 if (pos > par->size()) {
1736 lyxerr << "dont like 1 please report" << endl;
1739 } else if (pos > last + 1) {
1740 lyxerr << "dont like 2 please report" << endl;
1741 // This shouldn't happen.
1744 } else if (pos < row->pos()) {
1745 lyxerr << "dont like 3 please report" << endl;
1750 // now get the cursors x position
1751 float x = getCursorX(row, pos, last, boundary);
1754 if (old_row != row) {
1755 x = getCursorX(old_row, pos, last, boundary);
1759 //if the cursor is in a visible row, anchor to it
1761 if (topy < y && y < topy + bv()->workHeight())
1766 float LyXText::getCursorX(Row * row,
1767 pos_type pos, pos_type last, bool boundary) const
1769 pos_type cursor_vpos = 0;
1771 float fill_separator;
1773 float fill_label_hfill;
1774 // This call HAS to be here because of the BidiTables!!!
1775 prepareToPrint(row, x, fill_separator, fill_hfill,
1778 if (last < row->pos())
1779 cursor_vpos = row->pos();
1780 else if (pos > last && !boundary)
1781 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1782 ? row->pos() : last + 1;
1783 else if (pos > row->pos() &&
1784 (pos > last || boundary))
1785 /// Place cursor after char at (logical) position pos - 1
1786 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1787 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1789 /// Place cursor before char at (logical) position pos
1790 cursor_vpos = (bidi_level(pos) % 2 == 0)
1791 ? log2vis(pos) : log2vis(pos) + 1;
1793 pos_type body_pos = row->par()->beginningOfBody();
1794 if ((body_pos > 0) &&
1795 ((body_pos-1 > last) ||
1796 !row->par()->isLineSeparator(body_pos - 1)))
1799 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1800 pos_type pos = vis2log(vpos);
1801 if (body_pos > 0 && pos == body_pos - 1) {
1802 x += fill_label_hfill +
1803 font_metrics::width(
1804 row->par()->layout()->labelsep,
1805 getLabelFont(bv()->buffer(),
1807 if (row->par()->isLineSeparator(body_pos - 1))
1809 row->par(), body_pos - 1);
1811 if (row->hfillExpansion(pos)) {
1812 x += singleWidth(row->par(), pos);
1813 if (pos >= body_pos)
1816 x += fill_label_hfill;
1817 } else if (row->par()->isSeparator(pos)) {
1818 x += singleWidth(row->par(), pos);
1819 if (pos >= body_pos)
1820 x += fill_separator;
1822 x += singleWidth(row->par(), pos);
1828 void LyXText::setCursorIntern(Paragraph * par,
1829 pos_type pos, bool setfont, bool boundary)
1831 InsetText * it = static_cast<InsetText *>(par->inInset());
1833 if (it != inset_owner) {
1834 lyxerr[Debug::INSETS] << "InsetText is " << it
1836 << "inset_owner is "
1837 << inset_owner << endl;
1838 #ifdef WITH_WARNINGS
1839 #warning I believe this code is wrong. (Lgb)
1840 #warning Jürgen, have a look at this. (Lgb)
1841 #warning Hmmm, I guess you are right but we
1842 #warning should verify when this is needed
1844 // Jürgen, would you like to have a look?
1845 // I guess we need to move the outer cursor
1846 // and open and lock the inset (bla bla bla)
1847 // stuff I don't know... so can you have a look?
1849 // I moved the lyxerr stuff in here so we can see if
1850 // this is actually really needed and where!
1852 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1857 setCursor(cursor, par, pos, boundary);
1863 void LyXText::setCurrentFont()
1865 pos_type pos = cursor.pos();
1866 if (cursor.boundary() && pos > 0)
1870 if (pos == cursor.par()->size())
1872 else // potentional bug... BUG (Lgb)
1873 if (cursor.par()->isSeparator(pos)) {
1874 if (pos > cursor.row()->pos() &&
1875 bidi_level(pos) % 2 ==
1876 bidi_level(pos - 1) % 2)
1878 else if (pos + 1 < cursor.par()->size())
1884 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1885 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1887 if (cursor.pos() == cursor.par()->size() &&
1888 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1889 !cursor.boundary()) {
1890 Language const * lang =
1891 cursor.par()->getParLanguage(bv()->buffer()->params);
1892 current_font.setLanguage(lang);
1893 current_font.setNumber(LyXFont::OFF);
1894 real_current_font.setLanguage(lang);
1895 real_current_font.setNumber(LyXFont::OFF);
1900 // returns the column near the specified x-coordinate of the row
1901 // x is set to the real beginning of this column
1903 LyXText::getColumnNearX(Row * row, int & x,
1904 bool & boundary) const
1907 float fill_separator;
1909 float fill_label_hfill;
1911 prepareToPrint(row, tmpx, fill_separator,
1912 fill_hfill, fill_label_hfill);
1914 pos_type vc = row->pos();
1915 pos_type last = row->lastPrintablePos();
1918 LyXLayout_ptr const & layout = row->par()->layout();
1920 bool left_side = false;
1922 pos_type body_pos = row->par()->beginningOfBody();
1923 float last_tmpx = tmpx;
1926 (body_pos - 1 > last ||
1927 !row->par()->isLineSeparator(body_pos - 1)))
1930 // check for empty row
1931 if (!row->par()->size()) {
1936 while (vc <= last && tmpx <= x) {
1939 if (body_pos > 0 && c == body_pos-1) {
1940 tmpx += fill_label_hfill +
1941 font_metrics::width(layout->labelsep,
1942 getLabelFont(bv()->buffer(), row->par()));
1943 if (row->par()->isLineSeparator(body_pos - 1))
1944 tmpx -= singleWidth(row->par(), body_pos-1);
1947 if (row->hfillExpansion(c)) {
1948 tmpx += singleWidth(row->par(), c);
1952 tmpx += fill_label_hfill;
1953 } else if (row->par()->isSeparator(c)) {
1954 tmpx += singleWidth(row->par(), c);
1956 tmpx+= fill_separator;
1958 tmpx += singleWidth(row->par(), c);
1963 if ((tmpx + last_tmpx) / 2 > x) {
1968 if (vc > last + 1) // This shouldn't happen.
1972 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
1973 // some speedup if rtl_support=false
1974 && (!row->next() || row->next()->par() != row->par());
1975 bool const rtl = (lastrow)
1976 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
1977 : false; // If lastrow is false, we don't need to compute
1978 // the value of rtl.
1981 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
1982 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1984 else if (vc == row->pos()) {
1986 if (bidi_level(c) % 2 == 1)
1989 c = vis2log(vc - 1);
1990 bool const rtl = (bidi_level(c) % 2 == 1);
1991 if (left_side == rtl) {
1993 boundary = isBoundary(bv()->buffer(), row->par(), c);
1997 if (row->pos() <= last && c > last
1998 && row->par()->isNewline(last)) {
1999 if (bidi_level(last) % 2 == 0)
2000 tmpx -= singleWidth(row->par(), last);
2002 tmpx += singleWidth(row->par(), last);
2012 void LyXText::setCursorFromCoordinates(int x, int y)
2014 LyXCursor old_cursor = cursor;
2016 setCursorFromCoordinates(cursor, x, y);
2018 deleteEmptyParagraphMechanism(old_cursor);
2025 * return true if the cursor given is at the end of a row,
2026 * and the next row is filled by an inset that spans an entire
2029 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2032 Row const & next = *row.next();
2034 if (next.pos() != cur.pos() || next.par() != cur.par())
2036 if (!cur.par()->isInset(cur.pos()))
2038 Inset const * inset = cur.par()->getInset(cur.pos());
2039 if (inset->needFullRow() || inset->display())
2046 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2049 // Get the row first.
2051 Row * row = getRowNearY(y);
2053 pos_type const column = getColumnNearX(row, x, bound);
2054 cur.par(row->par());
2055 cur.pos(row->pos() + column);
2057 cur.y(y + row->baseline());
2060 if (beforeFullRowInset(*row, cur)) {
2061 pos_type last = row->lastPrintablePos();
2062 float x = getCursorX(row->next(), cur.pos(), last, bound);
2064 cur.iy(y + row->height() + row->next()->baseline());
2065 cur.irow(row->next());
2071 cur.boundary(bound);
2075 void LyXText::cursorLeft(bool internal)
2077 if (cursor.pos() > 0) {
2078 bool boundary = cursor.boundary();
2079 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2080 if (!internal && !boundary &&
2081 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2082 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2083 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2084 Paragraph * par = cursor.par()->previous();
2085 setCursor(par, par->size());
2090 void LyXText::cursorRight(bool internal)
2092 if (!internal && cursor.boundary() &&
2093 !cursor.par()->isNewline(cursor.pos()))
2094 setCursor(cursor.par(), cursor.pos(), true, false);
2095 else if (cursor.pos() < cursor.par()->size()) {
2096 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2098 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2099 setCursor(cursor.par(), cursor.pos(), true, true);
2100 } else if (cursor.par()->next())
2101 setCursor(cursor.par()->next(), 0);
2105 void LyXText::cursorUp(bool selecting)
2108 int x = cursor.x_fix();
2109 int y = cursor.y() - cursor.row()->baseline() - 1;
2110 setCursorFromCoordinates(x, y);
2113 int y1 = cursor.iy() - topy;
2116 Inset * inset_hit = checkInsetHit(x, y1);
2117 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2118 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2122 setCursorFromCoordinates(bv(), cursor.x_fix(),
2123 cursor.y() - cursor.row()->baseline() - 1);
2128 void LyXText::cursorDown(bool selecting)
2131 int x = cursor.x_fix();
2132 int y = cursor.y() - cursor.row()->baseline() +
2133 cursor.row()->height() + 1;
2134 setCursorFromCoordinates(x, y);
2135 if (!selecting && cursor.row() == cursor.irow()) {
2137 int y1 = cursor.iy() - topy;
2140 Inset * inset_hit = checkInsetHit(x, y1);
2141 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2142 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2146 setCursorFromCoordinates(bv(), cursor.x_fix(),
2147 cursor.y() - cursor.row()->baseline()
2148 + cursor.row()->height() + 1);
2153 void LyXText::cursorUpParagraph()
2155 if (cursor.pos() > 0) {
2156 setCursor(cursor.par(), 0);
2158 else if (cursor.par()->previous()) {
2159 setCursor(cursor.par()->previous(), 0);
2164 void LyXText::cursorDownParagraph()
2166 if (cursor.par()->next()) {
2167 setCursor(cursor.par()->next(), 0);
2169 setCursor(cursor.par(), cursor.par()->size());
2173 // fix the cursor `cur' after a characters has been deleted at `where'
2174 // position. Called by deleteEmptyParagraphMechanism
2175 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2176 LyXCursor const & where)
2178 // if cursor is not in the paragraph where the delete occured,
2180 if (cur.par() != where.par())
2183 // if cursor position is after the place where the delete occured,
2185 if (cur.pos() > where.pos())
2186 cur.pos(cur.pos()-1);
2188 // check also if we don't want to set the cursor on a spot behind the
2189 // pagragraph because we erased the last character.
2190 if (cur.pos() > cur.par()->size())
2191 cur.pos(cur.par()->size());
2193 // recompute row et al. for this cursor
2194 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2198 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2200 // Would be wrong to delete anything if we have a selection.
2201 if (selection.set())
2204 // We allow all kinds of "mumbo-jumbo" when freespacing.
2205 if (old_cursor.par()->layout()->free_spacing
2206 || old_cursor.par()->isFreeSpacing()) {
2210 /* Ok I'll put some comments here about what is missing.
2211 I have fixed BackSpace (and thus Delete) to not delete
2212 double-spaces automagically. I have also changed Cut,
2213 Copy and Paste to hopefully do some sensible things.
2214 There are still some small problems that can lead to
2215 double spaces stored in the document file or space at
2216 the beginning of paragraphs. This happens if you have
2217 the cursor betwenn to spaces and then save. Or if you
2218 cut and paste and the selection have a space at the
2219 beginning and then save right after the paste. I am
2220 sure none of these are very hard to fix, but I will
2221 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2222 that I can get some feedback. (Lgb)
2225 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2226 // delete the LineSeparator.
2229 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2230 // delete the LineSeparator.
2233 // If the pos around the old_cursor were spaces, delete one of them.
2234 if (old_cursor.par() != cursor.par()
2235 || old_cursor.pos() != cursor.pos()) {
2236 // Only if the cursor has really moved
2238 if (old_cursor.pos() > 0
2239 && old_cursor.pos() < old_cursor.par()->size()
2240 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2241 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2242 old_cursor.par()->erase(old_cursor.pos() - 1);
2243 redoParagraphs(old_cursor, old_cursor.par()->next());
2245 #ifdef WITH_WARNINGS
2246 #warning This will not work anymore when we have multiple views of the same buffer
2247 // In this case, we will have to correct also the cursors held by
2248 // other bufferviews. It will probably be easier to do that in a more
2249 // automated way in LyXCursor code. (JMarc 26/09/2001)
2251 // correct all cursors held by the LyXText
2252 fixCursorAfterDelete(cursor, old_cursor);
2253 fixCursorAfterDelete(selection.cursor,
2255 fixCursorAfterDelete(selection.start,
2257 fixCursorAfterDelete(selection.end, old_cursor);
2258 fixCursorAfterDelete(last_sel_cursor,
2260 fixCursorAfterDelete(toggle_cursor, old_cursor);
2261 fixCursorAfterDelete(toggle_end_cursor,
2267 // don't delete anything if this is the ONLY paragraph!
2268 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2271 // Do not delete empty paragraphs with keepempty set.
2272 if (old_cursor.par()->layout()->keepempty)
2275 // only do our magic if we changed paragraph
2276 if (old_cursor.par() == cursor.par())
2279 // record if we have deleted a paragraph
2280 // we can't possibly have deleted a paragraph before this point
2281 bool deleted = false;
2283 if ((old_cursor.par()->empty()
2284 || (old_cursor.par()->size() == 1
2285 && old_cursor.par()->isLineSeparator(0)))) {
2286 // ok, we will delete anything
2287 LyXCursor tmpcursor;
2291 if (old_cursor.row()->previous()) {
2292 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2293 - old_cursor.row()->previous()->height());
2295 cursor = old_cursor; // that undo can restore the right cursor position
2296 Paragraph * endpar = old_cursor.par()->next();
2297 if (endpar && endpar->getDepth()) {
2298 while (endpar && endpar->getDepth()) {
2299 endpar = endpar->next();
2302 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2306 removeRow(old_cursor.row());
2307 if (ownerParagraph() == old_cursor.par()) {
2308 ownerParagraph(ownerParagraph()->next());
2311 delete old_cursor.par();
2313 /* Breakagain the next par. Needed because of
2314 * the parindent that can occur or dissappear.
2315 * The next row can change its height, if
2316 * there is another layout before */
2318 if (refresh_row->next()) {
2319 breakAgain(refresh_row->next());
2322 setHeightOfRow(refresh_row);
2325 Row * nextrow = old_cursor.row()->next();
2326 const_cast<LyXText *>(this)->postPaint(
2327 old_cursor.y() - old_cursor.row()->baseline());
2330 cursor = old_cursor; // that undo can restore the right cursor position
2331 Paragraph * endpar = old_cursor.par()->next();
2332 if (endpar && endpar->getDepth()) {
2333 while (endpar && endpar->getDepth()) {
2334 endpar = endpar->next();
2337 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2341 removeRow(old_cursor.row());
2343 if (ownerParagraph() == old_cursor.par()) {
2344 ownerParagraph(ownerParagraph()->next());
2347 delete old_cursor.par();
2349 /* Breakagain the next par. Needed because of
2350 the parindent that can occur or dissappear.
2351 The next row can change its height, if
2352 there is another layout before */
2354 breakAgain(nextrow);
2360 setCursorIntern(cursor.par(), cursor.pos());
2362 if (selection.cursor.par() == old_cursor.par()
2363 && selection.cursor.pos() == old_cursor.pos()) {
2364 // correct selection
2365 selection.cursor = cursor;
2369 if (old_cursor.par()->stripLeadingSpaces()) {
2370 redoParagraphs(old_cursor,
2371 old_cursor.par()->next());
2373 setCursorIntern(cursor.par(), cursor.pos());
2374 selection.cursor = cursor;
2381 Paragraph * LyXText::ownerParagraph() const
2384 return inset_owner->paragraph();
2386 return &*(bv_owner->buffer()->paragraphs.begin());
2390 void LyXText::ownerParagraph(Paragraph * p) const
2393 inset_owner->paragraph(p);
2395 bv_owner->buffer()->paragraphs.set(p);
2400 void LyXText::ownerParagraph(int id, Paragraph * p) const
2402 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2403 if (op && op->inInset()) {
2404 static_cast<InsetText *>(op->inInset())->paragraph(p);
2411 LyXText::refresh_status LyXText::refreshStatus() const
2413 return refresh_status_;
2417 void LyXText::clearPaint()
2419 refresh_status_ = REFRESH_NONE;
2425 void LyXText::postPaint(int start_y)
2427 refresh_status old = refresh_status_;
2429 refresh_status_ = REFRESH_AREA;
2432 if (old != REFRESH_NONE && refresh_y < start_y)
2435 refresh_y = start_y;
2440 // We are an inset's lyxtext. Tell the top-level lyxtext
2441 // it needs to update the row we're in.
2442 LyXText * t = bv()->text;
2443 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2447 // FIXME: we should probably remove this y parameter,
2448 // make refresh_y be 0, and use row->y etc.
2449 void LyXText::postRowPaint(Row * row, int start_y)
2451 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2452 refresh_status_ = REFRESH_AREA;
2455 refresh_y = start_y;
2458 if (refresh_status_ == REFRESH_AREA)
2461 refresh_status_ = REFRESH_ROW;
2467 // We are an inset's lyxtext. Tell the top-level lyxtext
2468 // it needs to update the row we're in.
2469 LyXText * t = bv()->text;
2470 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2474 bool LyXText::isInInset() const
2476 // Sub-level has non-null bv owner and
2477 // non-null inset owner.
2478 return inset_owner != 0 && bv_owner != 0;
2482 int defaultRowHeight()
2484 LyXFont const font(LyXFont::ALL_SANE);
2485 return int(font_metrics::maxAscent(font)
2486 + font_metrics::maxDescent(font) * 1.5);