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.row()->next()
923 || cursor.row()->next()->par() != cursor.row()->par()) {
924 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
926 if (!cursor.par()->empty() &&
927 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
928 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
929 setCursor(cursor.par(), cursor.row()->lastPos());
931 setCursor(cursor.par(),
932 cursor.row()->lastPos() + 1);
938 void LyXText::cursorTop()
940 while (cursor.par()->previous())
941 cursor.par(cursor.par()->previous());
942 setCursor(cursor.par(), 0);
946 void LyXText::cursorBottom()
948 while (cursor.par()->next())
949 cursor.par(cursor.par()->next());
950 setCursor(cursor.par(), cursor.par()->size());
954 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
956 // If the mask is completely neutral, tell user
957 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
958 // Could only happen with user style
959 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
963 // Try implicit word selection
964 // If there is a change in the language the implicit word selection
966 LyXCursor resetCursor = cursor;
967 bool implicitSelection = (font.language() == ignore_language
968 && font.number() == LyXFont::IGNORE)
969 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
972 setFont(font, toggleall);
974 // Implicit selections are cleared afterwards
975 //and cursor is set to the original position.
976 if (implicitSelection) {
978 cursor = resetCursor;
979 setCursor(cursor.par(), cursor.pos());
980 selection.cursor = cursor;
983 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
987 string LyXText::getStringToIndex()
989 // Try implicit word selection
990 // If there is a change in the language the implicit word selection
992 LyXCursor const reset_cursor = cursor;
993 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
996 if (!selection.set())
997 bv()->owner()->message(_("Nothing to index!"));
998 else if (selection.start.par() != selection.end.par())
999 bv()->owner()->message(_("Cannot index more than one paragraph!"));
1001 idxstring = selectionAsString(bv()->buffer(), false);
1003 // Reset cursors to their original position.
1004 cursor = reset_cursor;
1005 setCursor(cursor.par(), cursor.pos());
1006 selection.cursor = cursor;
1008 // Clear the implicit selection.
1009 if (implicitSelection)
1016 // the DTP switches for paragraphs. LyX will store them in the first
1017 // physicla paragraph. When a paragraph is broken, the top settings rest,
1018 // the bottom settings are given to the new one. So I can make shure,
1019 // they do not duplicate themself and you cannnot make dirty things with
1022 void LyXText::setParagraph(bool line_top, bool line_bottom,
1023 bool pagebreak_top, bool pagebreak_bottom,
1024 VSpace const & space_top,
1025 VSpace const & space_bottom,
1026 Spacing const & spacing,
1028 string const & labelwidthstring,
1031 LyXCursor tmpcursor = cursor;
1032 if (!selection.set()) {
1033 selection.start = cursor;
1034 selection.end = cursor;
1037 // make sure that the depth behind the selection are restored, too
1038 Paragraph * endpar = selection.end.par()->next();
1039 Paragraph * undoendpar = endpar;
1041 if (endpar && endpar->getDepth()) {
1042 while (endpar && endpar->getDepth()) {
1043 endpar = endpar->next();
1044 undoendpar = endpar;
1048 // because of parindents etc.
1049 endpar = endpar->next();
1052 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1055 Paragraph * tmppar = selection.end.par();
1057 while (tmppar != selection.start.par()->previous()) {
1058 setCursor(tmppar, 0);
1059 postPaint(cursor.y() - cursor.row()->baseline());
1060 cursor.par()->params().lineTop(line_top);
1061 cursor.par()->params().lineBottom(line_bottom);
1062 cursor.par()->params().pagebreakTop(pagebreak_top);
1063 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1064 cursor.par()->params().spaceTop(space_top);
1065 cursor.par()->params().spaceBottom(space_bottom);
1066 cursor.par()->params().spacing(spacing);
1067 // does the layout allow the new alignment?
1068 LyXLayout_ptr const & layout = cursor.par()->layout();
1070 if (align == LYX_ALIGN_LAYOUT)
1071 align = layout->align;
1072 if (align & layout->alignpossible) {
1073 if (align == layout->align)
1074 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1076 cursor.par()->params().align(align);
1078 cursor.par()->setLabelWidthString(labelwidthstring);
1079 cursor.par()->params().noindent(noindent);
1080 tmppar = cursor.par()->previous();
1083 redoParagraphs(selection.start, endpar);
1086 setCursor(selection.start.par(), selection.start.pos());
1087 selection.cursor = cursor;
1088 setCursor(selection.end.par(), selection.end.pos());
1090 setCursor(tmpcursor.par(), tmpcursor.pos());
1092 bv()->updateInset(inset_owner);
1096 // set the counter of a paragraph. This includes the labels
1097 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1099 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1100 LyXLayout_ptr const & layout = par->layout();
1102 if (par->previous()) {
1104 par->params().appendix(par->previous()->params().appendix());
1105 if (!par->params().appendix() && par->params().startOfAppendix()) {
1106 par->params().appendix(true);
1107 textclass.counters().reset();
1109 par->enumdepth = par->previous()->enumdepth;
1110 par->itemdepth = par->previous()->itemdepth;
1112 par->params().appendix(par->params().startOfAppendix());
1117 /* Maybe we have to increment the enumeration depth.
1118 * BUT, enumeration in a footnote is considered in isolation from its
1119 * surrounding paragraph so don't increment if this is the
1120 * first line of the footnote
1121 * AND, bibliographies can't have their depth changed ie. they
1122 * are always of depth 0
1125 && par->previous()->getDepth() < par->getDepth()
1126 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1127 && par->enumdepth < 3
1128 && layout->labeltype != LABEL_BIBLIO) {
1132 // Maybe we have to decrement the enumeration depth, see note above
1134 && par->previous()->getDepth() > par->getDepth()
1135 && layout->labeltype != LABEL_BIBLIO) {
1136 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1139 if (!par->params().labelString().empty()) {
1140 par->params().labelString(string());
1143 if (layout->margintype == MARGIN_MANUAL) {
1144 if (par->params().labelWidthString().empty()) {
1145 par->setLabelWidthString(layout->labelstring());
1148 par->setLabelWidthString(string());
1151 // is it a layout that has an automatic label?
1152 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1153 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1157 if (i >= 0 && i <= buf->params.secnumdepth) {
1161 textclass.counters().step(layout->latexname());
1163 // Is there a label? Useful for Chapter layout
1164 if (!par->params().appendix()) {
1165 s << layout->labelstring();
1167 s << layout->labelstring_appendix();
1170 // Use of an integer is here less than elegant. For now.
1171 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1172 if (!par->params().appendix()) {
1173 numbertype = "sectioning";
1175 numbertype = "appendix";
1176 if (par->isRightToLeftPar(buf->params))
1177 langtype = "hebrew";
1182 s << textclass.counters()
1183 .numberLabel(layout->latexname(),
1184 numbertype, langtype, head);
1186 par->params().labelString(STRCONV(s.str()));
1188 // reset enum counters
1189 textclass.counters().reset("enum");
1190 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1191 textclass.counters().reset("enum");
1192 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1194 // Yes I know this is a really, really! bad solution
1196 string enumcounter("enum");
1198 switch (par->enumdepth) {
1207 enumcounter += "iv";
1210 // not a valid enumdepth...
1214 textclass.counters().step(enumcounter);
1216 s << textclass.counters()
1217 .numberLabel(enumcounter, "enumeration");
1218 par->params().labelString(STRCONV(s.str()));
1220 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1221 textclass.counters().step("bibitem");
1222 int number = textclass.counters().value("bibitem");
1223 if (par->bibitem()) {
1224 par->bibitem()->setCounter(number);
1225 par->params().labelString(layout->labelstring());
1227 // In biblio should't be following counters but...
1229 string s = layout->labelstring();
1231 // the caption hack:
1232 if (layout->labeltype == LABEL_SENSITIVE) {
1233 Paragraph * tmppar = par;
1236 while (tmppar && tmppar->inInset()
1237 // the single '=' is intended below
1238 && (in = tmppar->inInset()->owner())) {
1239 if (in->lyxCode() == Inset::FLOAT_CODE ||
1240 in->lyxCode() == Inset::WRAP_CODE) {
1244 tmppar = in->parOwner();
1250 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1252 textclass.counters().step(fl.type());
1254 // Doesn't work... yet.
1255 #warning use boost.format
1256 #if USE_BOOST_FORMAT
1257 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1258 // s << boost::format(_("%1$s %1$d:")
1260 // % buf->counters().value(fl.name());
1263 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1264 o << fl.name() << " #:";
1265 s = STRCONV(o.str());
1268 // par->SetLayout(0);
1269 // s = layout->labelstring;
1270 s = _("Senseless: ");
1273 par->params().labelString(s);
1275 // reset the enumeration counter. They are always reset
1276 // when there is any other layout between
1277 // Just fall-through between the cases so that all
1278 // enum counters deeper than enumdepth is also reset.
1279 switch (par->enumdepth) {
1281 textclass.counters().reset("enumi");
1283 textclass.counters().reset("enumii");
1285 textclass.counters().reset("enumiii");
1287 textclass.counters().reset("enumiv");
1293 // Updates all counters. Paragraphs with changed label string will be rebroken
1294 void LyXText::updateCounters()
1296 Row * row = firstRow();
1297 Paragraph * par = row->par();
1299 // CHECK if this is really needed. (Lgb)
1300 bv()->buffer()->params.getLyXTextClass().counters().reset();
1303 while (row->par() != par)
1306 string const oldLabel = par->params().labelString();
1308 // setCounter can potentially change the labelString.
1309 setCounter(bv()->buffer(), par);
1311 string const & newLabel = par->params().labelString();
1313 if (oldLabel.empty() && !newLabel.empty()) {
1314 removeParagraph(row);
1315 appendParagraph(row);
1323 void LyXText::insertInset(Inset * inset)
1325 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1327 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1329 cursor.par()->insertInset(cursor.pos(), inset);
1330 // Just to rebreak and refresh correctly.
1331 // The character will not be inserted a second time
1332 insertChar(Paragraph::META_INSET);
1333 // If we enter a highly editable inset the cursor should be to before
1334 // the inset. This couldn't happen before as Undo was not handled inside
1335 // inset now after the Undo LyX tries to call inset->Edit(...) again
1336 // and cannot do this as the cursor is behind the inset and GetInset
1337 // does not return the inset!
1338 if (isHighlyEditableInset(inset)) {
1345 void LyXText::copyEnvironmentType()
1347 copylayouttype = cursor.par()->layout()->name();
1351 void LyXText::pasteEnvironmentType()
1353 // do nothing if there has been no previous copyEnvironmentType()
1354 if (!copylayouttype.empty())
1355 setLayout(copylayouttype);
1359 void LyXText::cutSelection(bool doclear, bool realcut)
1361 // Stuff what we got on the clipboard. Even if there is no selection.
1363 // There is a problem with having the stuffing here in that the
1364 // larger the selection the slower LyX will get. This can be
1365 // solved by running the line below only when the selection has
1366 // finished. The solution used currently just works, to make it
1367 // faster we need to be more clever and probably also have more
1368 // calls to stuffClipboard. (Lgb)
1369 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1371 // This doesn't make sense, if there is no selection
1372 if (!selection.set())
1375 // OK, we have a selection. This is always between selection.start
1376 // and selection.end
1378 // make sure that the depth behind the selection are restored, too
1379 Paragraph * endpar = selection.end.par()->next();
1380 Paragraph * undoendpar = endpar;
1382 if (endpar && endpar->getDepth()) {
1383 while (endpar && endpar->getDepth()) {
1384 endpar = endpar->next();
1385 undoendpar = endpar;
1387 } else if (endpar) {
1388 endpar = endpar->next(); // because of parindents etc.
1391 setUndo(bv(), Undo::DELETE,
1392 selection.start.par(), undoendpar);
1394 // there are two cases: cut only within one paragraph or
1395 // more than one paragraph
1396 if (selection.start.par() == selection.end.par()) {
1397 // only within one paragraph
1398 endpar = selection.end.par();
1399 int pos = selection.end.pos();
1400 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1401 selection.start.pos(), pos,
1402 bv()->buffer()->params.textclass,
1404 selection.end.pos(pos);
1406 endpar = selection.end.par();
1407 int pos = selection.end.pos();
1408 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1409 selection.start.pos(), pos,
1410 bv()->buffer()->params.textclass,
1413 selection.end.par(endpar);
1414 selection.end.pos(pos);
1415 cursor.pos(selection.end.pos());
1417 endpar = endpar->next();
1419 // sometimes necessary
1421 selection.start.par()->stripLeadingSpaces();
1423 redoParagraphs(selection.start, endpar);
1425 // cutSelection can invalidate the cursor so we need to set
1427 // we prefer the end for when tracking changes
1428 cursor = selection.end;
1430 // need a valid cursor. (Lgb)
1433 setCursor(cursor.par(), cursor.pos());
1434 selection.cursor = cursor;
1439 void LyXText::copySelection()
1441 // stuff the selection onto the X clipboard, from an explicit copy request
1442 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1444 // this doesnt make sense, if there is no selection
1445 if (!selection.set())
1448 // ok we have a selection. This is always between selection.start
1449 // and sel_end cursor
1451 // copy behind a space if there is one
1452 while (selection.start.par()->size() > selection.start.pos()
1453 && selection.start.par()->isLineSeparator(selection.start.pos())
1454 && (selection.start.par() != selection.end.par()
1455 || selection.start.pos() < selection.end.pos()))
1456 selection.start.pos(selection.start.pos() + 1);
1458 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1459 selection.start.pos(), selection.end.pos(),
1460 bv()->buffer()->params.textclass);
1464 void LyXText::pasteSelection()
1466 // this does not make sense, if there is nothing to paste
1467 if (!CutAndPaste::checkPastePossible())
1470 setUndo(bv(), Undo::INSERT,
1471 cursor.par(), cursor.par()->next());
1474 Paragraph * actpar = cursor.par();
1475 int pos = cursor.pos();
1477 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1478 bv()->buffer()->params.textclass);
1480 redoParagraphs(cursor, endpar);
1482 setCursor(cursor.par(), cursor.pos());
1485 selection.cursor = cursor;
1486 setCursor(actpar, pos);
1492 void LyXText::setSelectionRange(lyx::pos_type length)
1497 selection.cursor = cursor;
1504 // simple replacing. The font of the first selected character is used
1505 void LyXText::replaceSelectionWithString(string const & str)
1507 setCursorParUndo(bv());
1510 if (!selection.set()) { // create a dummy selection
1511 selection.end = cursor;
1512 selection.start = cursor;
1515 // Get font setting before we cut
1516 pos_type pos = selection.end.pos();
1517 LyXFont const font = selection.start.par()
1518 ->getFontSettings(bv()->buffer()->params,
1519 selection.start.pos());
1521 // Insert the new string
1522 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1523 selection.end.par()->insertChar(pos, (*cit), font);
1527 // Cut the selection
1528 cutSelection(true, false);
1534 // needed to insert the selection
1535 void LyXText::insertStringAsLines(string const & str)
1537 Paragraph * par = cursor.par();
1538 pos_type pos = cursor.pos();
1539 Paragraph * endpar = cursor.par()->next();
1541 setCursorParUndo(bv());
1543 // only to be sure, should not be neccessary
1546 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1548 redoParagraphs(cursor, endpar);
1549 setCursor(cursor.par(), cursor.pos());
1550 selection.cursor = cursor;
1551 setCursor(par, pos);
1556 // turns double-CR to single CR, others where converted into one
1557 // blank. Then InsertStringAsLines is called
1558 void LyXText::insertStringAsParagraphs(string const & str)
1560 string linestr(str);
1561 bool newline_inserted = false;
1562 for (string::size_type i = 0; i < linestr.length(); ++i) {
1563 if (linestr[i] == '\n') {
1564 if (newline_inserted) {
1565 // we know that \r will be ignored by
1566 // InsertStringA. Of course, it is a dirty
1567 // trick, but it works...
1568 linestr[i - 1] = '\r';
1572 newline_inserted = true;
1574 } else if (IsPrintable(linestr[i])) {
1575 newline_inserted = false;
1578 insertStringAsLines(linestr);
1582 void LyXText::checkParagraph(Paragraph * par,
1585 LyXCursor tmpcursor;
1589 Row * row = getRow(par, pos, y);
1591 // is there a break one row above
1592 if (row->previous() && row->previous()->par() == row->par()) {
1593 z = rowBreakPoint(*row->previous());
1594 if (z >= row->pos()) {
1595 // set the dimensions of the row above
1596 y -= row->previous()->height();
1599 breakAgain(row->previous());
1601 // set the cursor again. Otherwise
1602 // dangling pointers are possible
1603 setCursor(cursor.par(), cursor.pos(),
1604 false, cursor.boundary());
1605 selection.cursor = cursor;
1610 int const tmpheight = row->height();
1611 pos_type const tmplast = row->lastPos();
1614 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1615 postRowPaint(row, y);
1620 // check the special right address boxes
1621 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1628 redoDrawingOfParagraph(tmpcursor);
1631 // set the cursor again. Otherwise dangling pointers are possible
1632 // also set the selection
1634 if (selection.set()) {
1636 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1637 false, selection.cursor.boundary());
1638 selection.cursor = cursor;
1639 setCursorIntern(selection.start.par(),
1640 selection.start.pos(),
1641 false, selection.start.boundary());
1642 selection.start = cursor;
1643 setCursorIntern(selection.end.par(),
1644 selection.end.pos(),
1645 false, selection.end.boundary());
1646 selection.end = cursor;
1647 setCursorIntern(last_sel_cursor.par(),
1648 last_sel_cursor.pos(),
1649 false, last_sel_cursor.boundary());
1650 last_sel_cursor = cursor;
1653 setCursorIntern(cursor.par(), cursor.pos(),
1654 false, cursor.boundary());
1658 // returns false if inset wasn't found
1659 bool LyXText::updateInset(Inset * inset)
1661 // first check the current paragraph
1662 int pos = cursor.par()->getPositionOfInset(inset);
1664 checkParagraph(cursor.par(), pos);
1668 // check every paragraph
1670 Paragraph * par = ownerParagraph();
1672 pos = par->getPositionOfInset(inset);
1674 checkParagraph(par, pos);
1684 bool LyXText::setCursor(Paragraph * par,
1686 bool setfont, bool boundary)
1688 LyXCursor old_cursor = cursor;
1689 setCursorIntern(par, pos, setfont, boundary);
1690 return deleteEmptyParagraphMechanism(old_cursor);
1694 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1695 pos_type pos, bool boundary)
1701 cur.boundary(boundary);
1703 // get the cursor y position in text
1705 Row * row = getRow(par, pos, y);
1706 Row * old_row = row;
1708 // if we are before the first char of this row and are still in the
1709 // same paragraph and there is a previous row then put the cursor on
1710 // the end of the previous row
1711 cur.iy(y + row->baseline());
1713 if (row->previous() && pos &&
1714 row->previous()->par() == row->par() &&
1715 pos < par->size() &&
1716 par->getChar(pos) == Paragraph::META_INSET &&
1717 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1719 row = row->previous();
1724 // y is now the beginning of the cursor row
1725 y += row->baseline();
1726 // y is now the cursor baseline
1729 pos_type last = old_row->lastPrintablePos();
1731 // None of these should happen, but we're scaredy-cats
1732 if (pos > par->size()) {
1733 lyxerr << "dont like 1 please report" << endl;
1736 } else if (pos > last + 1) {
1737 lyxerr << "dont like 2 please report" << endl;
1738 // This shouldn't happen.
1741 } else if (pos < row->pos()) {
1742 lyxerr << "dont like 3 please report" << endl;
1747 // now get the cursors x position
1748 float x = getCursorX(row, pos, last, boundary);
1751 if (old_row != row) {
1752 x = getCursorX(old_row, pos, last, boundary);
1756 //if the cursor is in a visible row, anchor to it
1758 if (topy < y && y < topy + bv()->workHeight())
1763 float LyXText::getCursorX(Row * row,
1764 pos_type pos, pos_type last, bool boundary) const
1766 pos_type cursor_vpos = 0;
1768 float fill_separator;
1770 float fill_label_hfill;
1771 // This call HAS to be here because of the BidiTables!!!
1772 prepareToPrint(row, x, fill_separator, fill_hfill,
1775 if (last < row->pos())
1776 cursor_vpos = row->pos();
1777 else if (pos > last && !boundary)
1778 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1779 ? row->pos() : last + 1;
1780 else if (pos > row->pos() &&
1781 (pos > last || boundary))
1782 /// Place cursor after char at (logical) position pos - 1
1783 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1784 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1786 /// Place cursor before char at (logical) position pos
1787 cursor_vpos = (bidi_level(pos) % 2 == 0)
1788 ? log2vis(pos) : log2vis(pos) + 1;
1790 pos_type body_pos = row->par()->beginningOfBody();
1791 if ((body_pos > 0) &&
1792 ((body_pos-1 > last) ||
1793 !row->par()->isLineSeparator(body_pos - 1)))
1796 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1797 pos_type pos = vis2log(vpos);
1798 if (body_pos > 0 && pos == body_pos - 1) {
1799 x += fill_label_hfill +
1800 font_metrics::width(
1801 row->par()->layout()->labelsep,
1802 getLabelFont(bv()->buffer(),
1804 if (row->par()->isLineSeparator(body_pos - 1))
1806 row->par(), body_pos - 1);
1808 if (row->hfillExpansion(pos)) {
1809 x += singleWidth(row->par(), pos);
1810 if (pos >= body_pos)
1813 x += fill_label_hfill;
1814 } else if (row->par()->isSeparator(pos)) {
1815 x += singleWidth(row->par(), pos);
1816 if (pos >= body_pos)
1817 x += fill_separator;
1819 x += singleWidth(row->par(), pos);
1825 void LyXText::setCursorIntern(Paragraph * par,
1826 pos_type pos, bool setfont, bool boundary)
1828 InsetText * it = static_cast<InsetText *>(par->inInset());
1830 if (it != inset_owner) {
1831 lyxerr[Debug::INSETS] << "InsetText is " << it
1833 << "inset_owner is "
1834 << inset_owner << endl;
1835 #ifdef WITH_WARNINGS
1836 #warning I believe this code is wrong. (Lgb)
1837 #warning Jürgen, have a look at this. (Lgb)
1838 #warning Hmmm, I guess you are right but we
1839 #warning should verify when this is needed
1841 // Jürgen, would you like to have a look?
1842 // I guess we need to move the outer cursor
1843 // and open and lock the inset (bla bla bla)
1844 // stuff I don't know... so can you have a look?
1846 // I moved the lyxerr stuff in here so we can see if
1847 // this is actually really needed and where!
1849 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1854 setCursor(cursor, par, pos, boundary);
1860 void LyXText::setCurrentFont()
1862 pos_type pos = cursor.pos();
1863 if (cursor.boundary() && pos > 0)
1867 if (pos == cursor.par()->size())
1869 else // potentional bug... BUG (Lgb)
1870 if (cursor.par()->isSeparator(pos)) {
1871 if (pos > cursor.row()->pos() &&
1872 bidi_level(pos) % 2 ==
1873 bidi_level(pos - 1) % 2)
1875 else if (pos + 1 < cursor.par()->size())
1881 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1882 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1884 if (cursor.pos() == cursor.par()->size() &&
1885 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1886 !cursor.boundary()) {
1887 Language const * lang =
1888 cursor.par()->getParLanguage(bv()->buffer()->params);
1889 current_font.setLanguage(lang);
1890 current_font.setNumber(LyXFont::OFF);
1891 real_current_font.setLanguage(lang);
1892 real_current_font.setNumber(LyXFont::OFF);
1897 // returns the column near the specified x-coordinate of the row
1898 // x is set to the real beginning of this column
1900 LyXText::getColumnNearX(Row * row, int & x,
1901 bool & boundary) const
1904 float fill_separator;
1906 float fill_label_hfill;
1908 prepareToPrint(row, tmpx, fill_separator,
1909 fill_hfill, fill_label_hfill);
1911 pos_type vc = row->pos();
1912 pos_type last = row->lastPrintablePos();
1915 LyXLayout_ptr const & layout = row->par()->layout();
1917 bool left_side = false;
1919 pos_type body_pos = row->par()->beginningOfBody();
1920 float last_tmpx = tmpx;
1923 (body_pos - 1 > last ||
1924 !row->par()->isLineSeparator(body_pos - 1)))
1927 // check for empty row
1928 if (!row->par()->size()) {
1933 while (vc <= last && tmpx <= x) {
1936 if (body_pos > 0 && c == body_pos-1) {
1937 tmpx += fill_label_hfill +
1938 font_metrics::width(layout->labelsep,
1939 getLabelFont(bv()->buffer(), row->par()));
1940 if (row->par()->isLineSeparator(body_pos - 1))
1941 tmpx -= singleWidth(row->par(), body_pos-1);
1944 if (row->hfillExpansion(c)) {
1945 tmpx += singleWidth(row->par(), c);
1949 tmpx += fill_label_hfill;
1950 } else if (row->par()->isSeparator(c)) {
1951 tmpx += singleWidth(row->par(), c);
1953 tmpx+= fill_separator;
1955 tmpx += singleWidth(row->par(), c);
1960 if ((tmpx + last_tmpx) / 2 > x) {
1965 if (vc > last + 1) // This shouldn't happen.
1969 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
1970 // some speedup if rtl_support=false
1971 && (!row->next() || row->next()->par() != row->par());
1972 bool const rtl = (lastrow)
1973 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
1974 : false; // If lastrow is false, we don't need to compute
1975 // the value of rtl.
1978 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
1979 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1981 else if (vc == row->pos()) {
1983 if (bidi_level(c) % 2 == 1)
1986 c = vis2log(vc - 1);
1987 bool const rtl = (bidi_level(c) % 2 == 1);
1988 if (left_side == rtl) {
1990 boundary = isBoundary(bv()->buffer(), row->par(), c);
1994 if (row->pos() <= last && c > last
1995 && row->par()->isNewline(last)) {
1996 if (bidi_level(last) % 2 == 0)
1997 tmpx -= singleWidth(row->par(), last);
1999 tmpx += singleWidth(row->par(), last);
2009 void LyXText::setCursorFromCoordinates(int x, int y)
2011 LyXCursor old_cursor = cursor;
2013 setCursorFromCoordinates(cursor, x, y);
2015 deleteEmptyParagraphMechanism(old_cursor);
2022 * return true if the cursor given is at the end of a row,
2023 * and the next row is filled by an inset that spans an entire
2026 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2029 Row const & next = *row.next();
2031 if (next.pos() != cur.pos() || next.par() != cur.par())
2033 if (!cur.par()->isInset(cur.pos()))
2035 Inset const * inset = cur.par()->getInset(cur.pos());
2036 if (inset->needFullRow() || inset->display())
2043 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2046 // Get the row first.
2048 Row * row = getRowNearY(y);
2050 pos_type const column = getColumnNearX(row, x, bound);
2051 cur.par(row->par());
2052 cur.pos(row->pos() + column);
2054 cur.y(y + row->baseline());
2057 if (beforeFullRowInset(*row, cur)) {
2058 pos_type last = row->lastPrintablePos();
2059 float x = getCursorX(row->next(), cur.pos(), last, bound);
2061 cur.iy(y + row->height() + row->next()->baseline());
2062 cur.irow(row->next());
2068 cur.boundary(bound);
2072 void LyXText::cursorLeft(bool internal)
2074 if (cursor.pos() > 0) {
2075 bool boundary = cursor.boundary();
2076 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2077 if (!internal && !boundary &&
2078 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2079 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2080 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2081 Paragraph * par = cursor.par()->previous();
2082 setCursor(par, par->size());
2087 void LyXText::cursorRight(bool internal)
2089 if (!internal && cursor.boundary() &&
2090 !cursor.par()->isNewline(cursor.pos()))
2091 setCursor(cursor.par(), cursor.pos(), true, false);
2092 else if (cursor.pos() < cursor.par()->size()) {
2093 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2095 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2096 setCursor(cursor.par(), cursor.pos(), true, true);
2097 } else if (cursor.par()->next())
2098 setCursor(cursor.par()->next(), 0);
2102 void LyXText::cursorUp(bool selecting)
2105 int x = cursor.x_fix();
2106 int y = cursor.y() - cursor.row()->baseline() - 1;
2107 setCursorFromCoordinates(x, y);
2110 int y1 = cursor.iy() - topy;
2113 Inset * inset_hit = checkInsetHit(x, y1);
2114 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2115 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2119 setCursorFromCoordinates(bv(), cursor.x_fix(),
2120 cursor.y() - cursor.row()->baseline() - 1);
2125 void LyXText::cursorDown(bool selecting)
2128 int x = cursor.x_fix();
2129 int y = cursor.y() - cursor.row()->baseline() +
2130 cursor.row()->height() + 1;
2131 setCursorFromCoordinates(x, y);
2132 if (!selecting && cursor.row() == cursor.irow()) {
2134 int y1 = cursor.iy() - topy;
2137 Inset * inset_hit = checkInsetHit(x, y1);
2138 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2139 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2143 setCursorFromCoordinates(bv(), cursor.x_fix(),
2144 cursor.y() - cursor.row()->baseline()
2145 + cursor.row()->height() + 1);
2150 void LyXText::cursorUpParagraph()
2152 if (cursor.pos() > 0) {
2153 setCursor(cursor.par(), 0);
2155 else if (cursor.par()->previous()) {
2156 setCursor(cursor.par()->previous(), 0);
2161 void LyXText::cursorDownParagraph()
2163 if (cursor.par()->next()) {
2164 setCursor(cursor.par()->next(), 0);
2166 setCursor(cursor.par(), cursor.par()->size());
2170 // fix the cursor `cur' after a characters has been deleted at `where'
2171 // position. Called by deleteEmptyParagraphMechanism
2172 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2173 LyXCursor const & where)
2175 // if cursor is not in the paragraph where the delete occured,
2177 if (cur.par() != where.par())
2180 // if cursor position is after the place where the delete occured,
2182 if (cur.pos() > where.pos())
2183 cur.pos(cur.pos()-1);
2185 // check also if we don't want to set the cursor on a spot behind the
2186 // pagragraph because we erased the last character.
2187 if (cur.pos() > cur.par()->size())
2188 cur.pos(cur.par()->size());
2190 // recompute row et al. for this cursor
2191 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2195 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2197 // Would be wrong to delete anything if we have a selection.
2198 if (selection.set())
2201 // We allow all kinds of "mumbo-jumbo" when freespacing.
2202 if (old_cursor.par()->layout()->free_spacing
2203 || old_cursor.par()->isFreeSpacing()) {
2207 /* Ok I'll put some comments here about what is missing.
2208 I have fixed BackSpace (and thus Delete) to not delete
2209 double-spaces automagically. I have also changed Cut,
2210 Copy and Paste to hopefully do some sensible things.
2211 There are still some small problems that can lead to
2212 double spaces stored in the document file or space at
2213 the beginning of paragraphs. This happens if you have
2214 the cursor betwenn to spaces and then save. Or if you
2215 cut and paste and the selection have a space at the
2216 beginning and then save right after the paste. I am
2217 sure none of these are very hard to fix, but I will
2218 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2219 that I can get some feedback. (Lgb)
2222 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2223 // delete the LineSeparator.
2226 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2227 // delete the LineSeparator.
2230 // If the pos around the old_cursor were spaces, delete one of them.
2231 if (old_cursor.par() != cursor.par()
2232 || old_cursor.pos() != cursor.pos()) {
2233 // Only if the cursor has really moved
2235 if (old_cursor.pos() > 0
2236 && old_cursor.pos() < old_cursor.par()->size()
2237 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2238 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2239 old_cursor.par()->erase(old_cursor.pos() - 1);
2240 redoParagraphs(old_cursor, old_cursor.par()->next());
2242 #ifdef WITH_WARNINGS
2243 #warning This will not work anymore when we have multiple views of the same buffer
2244 // In this case, we will have to correct also the cursors held by
2245 // other bufferviews. It will probably be easier to do that in a more
2246 // automated way in LyXCursor code. (JMarc 26/09/2001)
2248 // correct all cursors held by the LyXText
2249 fixCursorAfterDelete(cursor, old_cursor);
2250 fixCursorAfterDelete(selection.cursor,
2252 fixCursorAfterDelete(selection.start,
2254 fixCursorAfterDelete(selection.end, old_cursor);
2255 fixCursorAfterDelete(last_sel_cursor,
2257 fixCursorAfterDelete(toggle_cursor, old_cursor);
2258 fixCursorAfterDelete(toggle_end_cursor,
2264 // don't delete anything if this is the ONLY paragraph!
2265 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2268 // Do not delete empty paragraphs with keepempty set.
2269 if (old_cursor.par()->layout()->keepempty)
2272 // only do our magic if we changed paragraph
2273 if (old_cursor.par() == cursor.par())
2276 // record if we have deleted a paragraph
2277 // we can't possibly have deleted a paragraph before this point
2278 bool deleted = false;
2280 if ((old_cursor.par()->empty()
2281 || (old_cursor.par()->size() == 1
2282 && old_cursor.par()->isLineSeparator(0)))) {
2283 // ok, we will delete anything
2284 LyXCursor tmpcursor;
2288 if (old_cursor.row()->previous()) {
2289 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2290 - old_cursor.row()->previous()->height());
2292 cursor = old_cursor; // that undo can restore the right cursor position
2293 Paragraph * endpar = old_cursor.par()->next();
2294 if (endpar && endpar->getDepth()) {
2295 while (endpar && endpar->getDepth()) {
2296 endpar = endpar->next();
2299 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2303 removeRow(old_cursor.row());
2304 if (ownerParagraph() == old_cursor.par()) {
2305 ownerParagraph(ownerParagraph()->next());
2308 delete old_cursor.par();
2310 /* Breakagain the next par. Needed because of
2311 * the parindent that can occur or dissappear.
2312 * The next row can change its height, if
2313 * there is another layout before */
2315 if (refresh_row->next()) {
2316 breakAgain(refresh_row->next());
2319 setHeightOfRow(refresh_row);
2322 Row * nextrow = old_cursor.row()->next();
2323 const_cast<LyXText *>(this)->postPaint(
2324 old_cursor.y() - old_cursor.row()->baseline());
2327 cursor = old_cursor; // that undo can restore the right cursor position
2328 Paragraph * endpar = old_cursor.par()->next();
2329 if (endpar && endpar->getDepth()) {
2330 while (endpar && endpar->getDepth()) {
2331 endpar = endpar->next();
2334 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2338 removeRow(old_cursor.row());
2340 if (ownerParagraph() == old_cursor.par()) {
2341 ownerParagraph(ownerParagraph()->next());
2344 delete old_cursor.par();
2346 /* Breakagain the next par. Needed because of
2347 the parindent that can occur or dissappear.
2348 The next row can change its height, if
2349 there is another layout before */
2351 breakAgain(nextrow);
2357 setCursorIntern(cursor.par(), cursor.pos());
2359 if (selection.cursor.par() == old_cursor.par()
2360 && selection.cursor.pos() == old_cursor.pos()) {
2361 // correct selection
2362 selection.cursor = cursor;
2366 if (old_cursor.par()->stripLeadingSpaces()) {
2367 redoParagraphs(old_cursor,
2368 old_cursor.par()->next());
2370 setCursorIntern(cursor.par(), cursor.pos());
2371 selection.cursor = cursor;
2378 Paragraph * LyXText::ownerParagraph() const
2381 return inset_owner->paragraph();
2383 return &*(bv_owner->buffer()->paragraphs.begin());
2387 void LyXText::ownerParagraph(Paragraph * p) const
2390 inset_owner->paragraph(p);
2392 bv_owner->buffer()->paragraphs.set(p);
2397 void LyXText::ownerParagraph(int id, Paragraph * p) const
2399 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2400 if (op && op->inInset()) {
2401 static_cast<InsetText *>(op->inInset())->paragraph(p);
2408 LyXText::refresh_status LyXText::refreshStatus() const
2410 return refresh_status_;
2414 void LyXText::clearPaint()
2416 refresh_status_ = REFRESH_NONE;
2422 void LyXText::postPaint(int start_y)
2424 refresh_status old = refresh_status_;
2426 refresh_status_ = REFRESH_AREA;
2429 if (old != REFRESH_NONE && refresh_y < start_y)
2432 refresh_y = start_y;
2437 // We are an inset's lyxtext. Tell the top-level lyxtext
2438 // it needs to update the row we're in.
2439 LyXText * t = bv()->text;
2440 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2444 // FIXME: we should probably remove this y parameter,
2445 // make refresh_y be 0, and use row->y etc.
2446 void LyXText::postRowPaint(Row * row, int start_y)
2448 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2449 refresh_status_ = REFRESH_AREA;
2452 refresh_y = start_y;
2455 if (refresh_status_ == REFRESH_AREA)
2458 refresh_status_ = REFRESH_ROW;
2464 // We are an inset's lyxtext. Tell the top-level lyxtext
2465 // it needs to update the row we're in.
2466 LyXText * t = bv()->text;
2467 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2471 bool LyXText::isInInset() const
2473 // Sub-level has non-null bv owner and
2474 // non-null inset owner.
2475 return inset_owner != 0 && bv_owner != 0;
2479 int defaultRowHeight()
2481 LyXFont const font(LyXFont::ALL_SANE);
2482 return int(font_metrics::maxAscent(font)
2483 + font_metrics::maxDescent(font) * 1.5);