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, rowlist_.end());
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
267 LyXText::insertRow(RowList::iterator rowit, Paragraph * par,
270 Row * tmprow = new Row;
274 if (rowit == rowlist_.end())
275 return rowlist_.insert(rowlist_.begin(), tmprow);
277 return rowlist_.insert(boost::next(rowit), 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, RowList::iterator rowit)
331 // insert a new row, starting at position 0
332 RowList::iterator rit = insertRow(rowit, par, 0);
334 // and now append the whole paragraph before the new row
335 appendParagraph(rit);
339 Inset * LyXText::getInset() const
341 if (cursor.pos() < cursor.par()->size()
342 && cursor.par()->isInset(cursor.pos())) {
343 return cursor.par()->getInset(cursor.pos());
349 void LyXText::toggleInset()
351 Inset * inset = getInset();
352 // is there an editable inset at cursor position?
353 if (!isEditableInset(inset)) {
354 // No, try to see if we are inside a collapsable inset
355 if (inset_owner && inset_owner->owner()
356 && inset_owner->owner()->isOpen()) {
357 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
358 inset_owner->owner()->close(bv());
359 bv()->getLyXText()->cursorRight(bv());
363 //bv()->owner()->message(inset->editMessage());
365 // do we want to keep this?? (JMarc)
366 if (!isHighlyEditableInset(inset))
367 setCursorParUndo(bv());
369 if (inset->isOpen()) {
375 bv()->updateInset(inset);
379 /* used in setlayout */
380 // Asger is not sure we want to do this...
381 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
384 LyXLayout_ptr const & layout = par.layout();
387 for (pos_type pos = 0; pos < par.size(); ++pos) {
388 if (pos < par.beginningOfBody())
389 layoutfont = layout->labelfont;
391 layoutfont = layout->font;
393 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
394 tmpfont.reduce(layoutfont);
395 par.setFont(pos, tmpfont);
400 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
401 LyXCursor & send_cur,
402 string const & layout)
404 Paragraph * endpar = send_cur.par()->next();
405 Paragraph * undoendpar = endpar;
407 if (endpar && endpar->getDepth()) {
408 while (endpar && endpar->getDepth()) {
409 endpar = endpar->next();
413 endpar = endpar->next(); // because of parindents etc.
416 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
418 // ok we have a selection. This is always between sstart_cur
419 // and sel_end cursor
421 Paragraph * par = sstart_cur.par();
422 Paragraph * epar = send_cur.par()->next();
424 LyXLayout_ptr const & lyxlayout =
425 bv()->buffer()->params.getLyXTextClass()[layout];
428 par->applyLayout(lyxlayout);
429 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
430 Paragraph * fppar = par;
431 fppar->params().spaceTop(lyxlayout->fill_top ?
432 VSpace(VSpace::VFILL)
433 : VSpace(VSpace::NONE));
434 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
435 VSpace(VSpace::VFILL)
436 : VSpace(VSpace::NONE));
437 if (lyxlayout->margintype == MARGIN_MANUAL)
438 par->setLabelWidthString(lyxlayout->labelstring());
441 } while (par != epar);
447 // set layout over selection and make a total rebreak of those paragraphs
448 void LyXText::setLayout(string const & layout)
450 LyXCursor tmpcursor = cursor; /* store the current cursor */
452 // if there is no selection just set the layout
453 // of the current paragraph */
454 if (!selection.set()) {
455 selection.start = cursor; // dummy selection
456 selection.end = cursor;
458 Paragraph * endpar = setLayout(cursor, selection.start,
459 selection.end, layout);
460 redoParagraphs(selection.start, endpar);
462 // we have to reset the selection, because the
463 // geometry could have changed
464 setCursor(selection.start.par(),
465 selection.start.pos(), false);
466 selection.cursor = cursor;
467 setCursor(selection.end.par(), selection.end.pos(), false);
471 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
475 // increment depth over selection and
476 // make a total rebreak of those paragraphs
477 void LyXText::incDepth()
479 // If there is no selection, just use the current paragraph
480 if (!selection.set()) {
481 selection.start = cursor; // dummy selection
482 selection.end = cursor;
485 // We end at the next paragraph with depth 0
486 Paragraph * endpar = selection.end.par()->next();
488 Paragraph * undoendpar = endpar;
490 if (endpar && endpar->getDepth()) {
491 while (endpar && endpar->getDepth()) {
492 endpar = endpar->next();
496 endpar = endpar->next(); // because of parindents etc.
499 setUndo(bv(), Undo::EDIT,
500 selection.start.par(), undoendpar);
502 LyXCursor tmpcursor = cursor; // store the current cursor
504 // ok we have a selection. This is always between sel_start_cursor
505 // and sel_end cursor
506 cursor = selection.start;
509 // NOTE: you can't change the depth of a bibliography entry
510 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
511 Paragraph * prev = cursor.par()->previous();
514 if (cursor.par()->getDepth()
515 < prev->getMaxDepthAfter()) {
516 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
520 if (cursor.par() == selection.end.par())
522 cursor.par(cursor.par()->next());
525 redoParagraphs(selection.start, endpar);
527 // we have to reset visual the selection because the
528 // geometry could have changed
529 setCursor(selection.start.par(), selection.start.pos());
530 selection.cursor = cursor;
531 setCursor(selection.end.par(), selection.end.pos());
534 setCursor(tmpcursor.par(), tmpcursor.pos());
538 // decrement depth over selection and
539 // make a total rebreak of those paragraphs
540 void LyXText::decDepth()
542 // if there is no selection just set the layout
543 // of the current paragraph
544 if (!selection.set()) {
545 selection.start = cursor; // dummy selection
546 selection.end = cursor;
548 Paragraph * endpar = selection.end.par()->next();
549 Paragraph * undoendpar = endpar;
551 if (endpar && endpar->getDepth()) {
552 while (endpar && endpar->getDepth()) {
553 endpar = endpar->next();
557 endpar = endpar->next(); // because of parindents etc.
560 setUndo(bv(), Undo::EDIT,
561 selection.start.par(), undoendpar);
563 LyXCursor tmpcursor = cursor; // store the current cursor
565 // ok we have a selection. This is always between sel_start_cursor
566 // and sel_end cursor
567 cursor = selection.start;
570 if (cursor.par()->params().depth()) {
571 cursor.par()->params()
572 .depth(cursor.par()->params().depth() - 1);
574 if (cursor.par() == selection.end.par()) {
577 cursor.par(cursor.par()->next());
580 redoParagraphs(selection.start, endpar);
582 // we have to reset the visual selection because the
583 // geometry could have changed
584 setCursor(selection.start.par(), selection.start.pos());
585 selection.cursor = cursor;
586 setCursor(selection.end.par(), selection.end.pos());
589 setCursor(tmpcursor.par(), tmpcursor.pos());
593 // set font over selection and make a total rebreak of those paragraphs
594 void LyXText::setFont(LyXFont const & font, bool toggleall)
596 // if there is no selection just set the current_font
597 if (!selection.set()) {
598 // Determine basis font
600 if (cursor.pos() < cursor.par()->beginningOfBody()) {
601 layoutfont = getLabelFont(bv()->buffer(),
604 layoutfont = getLayoutFont(bv()->buffer(),
607 // Update current font
608 real_current_font.update(font,
609 bv()->buffer()->params.language,
612 // Reduce to implicit settings
613 current_font = real_current_font;
614 current_font.reduce(layoutfont);
615 // And resolve it completely
616 real_current_font.realize(layoutfont);
621 LyXCursor tmpcursor = cursor; // store the current cursor
623 // ok we have a selection. This is always between sel_start_cursor
624 // and sel_end cursor
626 setUndo(bv(), Undo::EDIT,
627 selection.start.par(), selection.end.par()->next());
629 cursor = selection.start;
630 while (cursor.par() != selection.end.par() ||
631 cursor.pos() < selection.end.pos())
633 if (cursor.pos() < cursor.par()->size()) {
634 // an open footnote should behave like a closed one
635 setCharFont(cursor.par(), cursor.pos(),
637 cursor.pos(cursor.pos() + 1);
640 cursor.par(cursor.par()->next());
645 redoParagraphs(selection.start, selection.end.par()->next());
647 // we have to reset the selection, because the
648 // geometry could have changed, but we keep
649 // it for user convenience
650 setCursor(selection.start.par(), selection.start.pos());
651 selection.cursor = cursor;
652 setCursor(selection.end.par(), selection.end.pos());
654 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
655 tmpcursor.boundary());
659 void LyXText::redoHeightOfParagraph()
661 Row * tmprow = cursor.row();
662 int y = cursor.y() - tmprow->baseline();
664 setHeightOfRow(tmprow);
666 while (tmprow->previous()
667 && tmprow->previous()->par() == tmprow->par()) {
668 tmprow = tmprow->previous();
669 y -= tmprow->height();
670 setHeightOfRow(tmprow);
675 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
679 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
681 Row * tmprow = cur.row();
683 int y = cur.y() - tmprow->baseline();
684 setHeightOfRow(tmprow);
686 while (tmprow->previous()
687 && tmprow->previous()->par() == tmprow->par()) {
688 tmprow = tmprow->previous();
689 y -= tmprow->height();
693 setCursor(cur.par(), cur.pos());
697 // deletes and inserts again all paragaphs between the cursor
698 // and the specified par
699 // This function is needed after SetLayout and SetFont etc.
700 void LyXText::redoParagraphs(LyXCursor const & cur,
701 Paragraph const * endpar)
703 Row * tmprow = cur.row();
705 int y = cur.y() - tmprow->baseline();
707 Paragraph * first_phys_par = 0;
708 if (!tmprow->previous()) {
709 // a trick/hack for UNDO
710 // This is needed because in an UNDO/REDO we could have changed
711 // the ownerParagrah() so the paragraph inside the row is NOT
712 // my really first par anymore. Got it Lars ;) (Jug 20011206)
713 first_phys_par = ownerParagraph();
715 first_phys_par = tmprow->par();
717 // Find first row of this paragraph.
718 while (tmprow->previous()
719 && tmprow->previous()->par() == first_phys_par)
721 tmprow = tmprow->previous();
722 y -= tmprow->height();
726 Row * prevrow = tmprow->previous();
728 // Remove all the rows until we reach endpar
729 Paragraph * tmppar = 0;
731 tmppar = tmprow->next()->par();
732 while (tmprow->next() && tmppar != endpar) {
733 removeRow(tmprow->next());
734 if (tmprow->next()) {
735 tmppar = tmprow->next()->par();
741 // Remove the first of the paragraphs rows.
742 // This is because tmprow->previous() can be 0
743 Row * tmprow2 = tmprow;
744 tmprow = tmprow->previous();
747 // Reinsert the paragraphs.
748 tmppar = first_phys_par;
751 insertParagraph(tmppar, tmprow);
756 while (tmprow->next()
757 && tmprow->next()->par() == tmppar) {
758 tmprow = tmprow->next();
760 tmppar = tmppar->next();
762 } while (tmppar && tmppar != endpar);
764 // this is because of layout changes
766 setHeightOfRow(prevrow);
767 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
769 setHeightOfRow(firstRow());
770 const_cast<LyXText *>(this)->postPaint(0);
773 if (tmprow && tmprow->next())
774 setHeightOfRow(tmprow->next());
779 void LyXText::fullRebreak()
785 if (need_break_row) {
786 breakAgain(need_break_row);
793 // important for the screen
796 // the cursor set functions have a special mechanism. When they
797 // realize, that you left an empty paragraph, they will delete it.
798 // They also delete the corresponding row
800 // need the selection cursor:
801 void LyXText::setSelection()
803 bool const lsel = selection.set();
805 if (!selection.set()) {
806 last_sel_cursor = selection.cursor;
807 selection.start = selection.cursor;
808 selection.end = selection.cursor;
813 // first the toggling area
814 if (cursor.y() < last_sel_cursor.y()
815 || (cursor.y() == last_sel_cursor.y()
816 && cursor.x() < last_sel_cursor.x())) {
817 toggle_end_cursor = last_sel_cursor;
818 toggle_cursor = cursor;
820 toggle_end_cursor = cursor;
821 toggle_cursor = last_sel_cursor;
824 last_sel_cursor = cursor;
826 // and now the whole selection
828 if (selection.cursor.par() == cursor.par())
829 if (selection.cursor.pos() < cursor.pos()) {
830 selection.end = cursor;
831 selection.start = selection.cursor;
833 selection.end = selection.cursor;
834 selection.start = cursor;
836 else if (selection.cursor.y() < cursor.y() ||
837 (selection.cursor.y() == cursor.y()
838 && selection.cursor.x() < cursor.x())) {
839 selection.end = cursor;
840 selection.start = selection.cursor;
843 selection.end = selection.cursor;
844 selection.start = cursor;
847 // a selection with no contents is not a selection
848 if (selection.start.par() == selection.end.par() &&
849 selection.start.pos() == selection.end.pos())
850 selection.set(false);
852 if (inset_owner && (selection.set() || lsel))
853 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
857 string const LyXText::selectionAsString(Buffer const * buffer,
860 if (!selection.set()) return string();
862 // should be const ...
863 Paragraph * startpar(selection.start.par());
864 Paragraph * endpar(selection.end.par());
865 pos_type const startpos(selection.start.pos());
866 pos_type const endpos(selection.end.pos());
868 if (startpar == endpar) {
869 return startpar->asString(buffer, startpos, endpos, label);
874 // First paragraph in selection
875 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
877 // The paragraphs in between (if any)
878 LyXCursor tmpcur(selection.start);
879 tmpcur.par(tmpcur.par()->next());
880 while (tmpcur.par() != endpar) {
881 result += tmpcur.par()->asString(buffer, 0,
882 tmpcur.par()->size(),
884 tmpcur.par(tmpcur.par()->next());
887 // Last paragraph in selection
888 result += endpar->asString(buffer, 0, endpos, label);
894 void LyXText::clearSelection()
896 selection.set(false);
897 selection.mark(false);
898 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
899 // reset this in the bv_owner!
900 if (bv_owner && bv_owner->text)
901 bv_owner->text->xsel_cache.set(false);
905 void LyXText::cursorHome()
907 setCursor(cursor.par(), cursor.row()->pos());
911 void LyXText::cursorEnd()
913 if (cursor.par()->empty())
916 if (!cursor.row()->next()
917 || cursor.row()->next()->par() != cursor.row()->par()) {
918 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
920 if (!cursor.par()->empty() &&
921 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
922 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
923 setCursor(cursor.par(), cursor.row()->lastPos());
925 setCursor(cursor.par(),
926 cursor.row()->lastPos() + 1);
932 void LyXText::cursorTop()
934 while (cursor.par()->previous())
935 cursor.par(cursor.par()->previous());
936 setCursor(cursor.par(), 0);
940 void LyXText::cursorBottom()
942 while (cursor.par()->next())
943 cursor.par(cursor.par()->next());
944 setCursor(cursor.par(), cursor.par()->size());
948 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
950 // If the mask is completely neutral, tell user
951 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
952 // Could only happen with user style
953 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
957 // Try implicit word selection
958 // If there is a change in the language the implicit word selection
960 LyXCursor resetCursor = cursor;
961 bool implicitSelection = (font.language() == ignore_language
962 && font.number() == LyXFont::IGNORE)
963 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
966 setFont(font, toggleall);
968 // Implicit selections are cleared afterwards
969 //and cursor is set to the original position.
970 if (implicitSelection) {
972 cursor = resetCursor;
973 setCursor(cursor.par(), cursor.pos());
974 selection.cursor = cursor;
977 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
981 string LyXText::getStringToIndex()
983 // Try implicit word selection
984 // If there is a change in the language the implicit word selection
986 LyXCursor const reset_cursor = cursor;
987 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
990 if (!selection.set())
991 bv()->owner()->message(_("Nothing to index!"));
992 else if (selection.start.par() != selection.end.par())
993 bv()->owner()->message(_("Cannot index more than one paragraph!"));
995 idxstring = selectionAsString(bv()->buffer(), false);
997 // Reset cursors to their original position.
998 cursor = reset_cursor;
999 setCursor(cursor.par(), cursor.pos());
1000 selection.cursor = cursor;
1002 // Clear the implicit selection.
1003 if (implicitSelection)
1010 // the DTP switches for paragraphs. LyX will store them in the first
1011 // physicla paragraph. When a paragraph is broken, the top settings rest,
1012 // the bottom settings are given to the new one. So I can make shure,
1013 // they do not duplicate themself and you cannnot make dirty things with
1016 void LyXText::setParagraph(bool line_top, bool line_bottom,
1017 bool pagebreak_top, bool pagebreak_bottom,
1018 VSpace const & space_top,
1019 VSpace const & space_bottom,
1020 Spacing const & spacing,
1022 string const & labelwidthstring,
1025 LyXCursor tmpcursor = cursor;
1026 if (!selection.set()) {
1027 selection.start = cursor;
1028 selection.end = cursor;
1031 // make sure that the depth behind the selection are restored, too
1032 Paragraph * endpar = selection.end.par()->next();
1033 Paragraph * undoendpar = endpar;
1035 if (endpar && endpar->getDepth()) {
1036 while (endpar && endpar->getDepth()) {
1037 endpar = endpar->next();
1038 undoendpar = endpar;
1042 // because of parindents etc.
1043 endpar = endpar->next();
1046 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1049 Paragraph * tmppar = selection.end.par();
1051 while (tmppar != selection.start.par()->previous()) {
1052 setCursor(tmppar, 0);
1053 postPaint(cursor.y() - cursor.row()->baseline());
1054 cursor.par()->params().lineTop(line_top);
1055 cursor.par()->params().lineBottom(line_bottom);
1056 cursor.par()->params().pagebreakTop(pagebreak_top);
1057 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1058 cursor.par()->params().spaceTop(space_top);
1059 cursor.par()->params().spaceBottom(space_bottom);
1060 cursor.par()->params().spacing(spacing);
1061 // does the layout allow the new alignment?
1062 LyXLayout_ptr const & layout = cursor.par()->layout();
1064 if (align == LYX_ALIGN_LAYOUT)
1065 align = layout->align;
1066 if (align & layout->alignpossible) {
1067 if (align == layout->align)
1068 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1070 cursor.par()->params().align(align);
1072 cursor.par()->setLabelWidthString(labelwidthstring);
1073 cursor.par()->params().noindent(noindent);
1074 tmppar = cursor.par()->previous();
1077 redoParagraphs(selection.start, endpar);
1080 setCursor(selection.start.par(), selection.start.pos());
1081 selection.cursor = cursor;
1082 setCursor(selection.end.par(), selection.end.pos());
1084 setCursor(tmpcursor.par(), tmpcursor.pos());
1086 bv()->updateInset(inset_owner);
1090 // set the counter of a paragraph. This includes the labels
1091 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1093 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1094 LyXLayout_ptr const & layout = par->layout();
1096 if (par->previous()) {
1098 par->params().appendix(par->previous()->params().appendix());
1099 if (!par->params().appendix() && par->params().startOfAppendix()) {
1100 par->params().appendix(true);
1101 textclass.counters().reset();
1103 par->enumdepth = par->previous()->enumdepth;
1104 par->itemdepth = par->previous()->itemdepth;
1106 par->params().appendix(par->params().startOfAppendix());
1111 /* Maybe we have to increment the enumeration depth.
1112 * BUT, enumeration in a footnote is considered in isolation from its
1113 * surrounding paragraph so don't increment if this is the
1114 * first line of the footnote
1115 * AND, bibliographies can't have their depth changed ie. they
1116 * are always of depth 0
1119 && par->previous()->getDepth() < par->getDepth()
1120 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1121 && par->enumdepth < 3
1122 && layout->labeltype != LABEL_BIBLIO) {
1126 // Maybe we have to decrement the enumeration depth, see note above
1128 && par->previous()->getDepth() > par->getDepth()
1129 && layout->labeltype != LABEL_BIBLIO) {
1130 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1133 if (!par->params().labelString().empty()) {
1134 par->params().labelString(string());
1137 if (layout->margintype == MARGIN_MANUAL) {
1138 if (par->params().labelWidthString().empty()) {
1139 par->setLabelWidthString(layout->labelstring());
1142 par->setLabelWidthString(string());
1145 // is it a layout that has an automatic label?
1146 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1147 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1151 if (i >= 0 && i <= buf->params.secnumdepth) {
1155 textclass.counters().step(layout->latexname());
1157 // Is there a label? Useful for Chapter layout
1158 if (!par->params().appendix()) {
1159 s << layout->labelstring();
1161 s << layout->labelstring_appendix();
1164 // Use of an integer is here less than elegant. For now.
1165 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1166 if (!par->params().appendix()) {
1167 numbertype = "sectioning";
1169 numbertype = "appendix";
1170 if (par->isRightToLeftPar(buf->params))
1171 langtype = "hebrew";
1176 s << textclass.counters()
1177 .numberLabel(layout->latexname(),
1178 numbertype, langtype, head);
1180 par->params().labelString(STRCONV(s.str()));
1182 // reset enum counters
1183 textclass.counters().reset("enum");
1184 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1185 textclass.counters().reset("enum");
1186 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1188 // Yes I know this is a really, really! bad solution
1190 string enumcounter("enum");
1192 switch (par->enumdepth) {
1201 enumcounter += "iv";
1204 // not a valid enumdepth...
1208 textclass.counters().step(enumcounter);
1210 s << textclass.counters()
1211 .numberLabel(enumcounter, "enumeration");
1212 par->params().labelString(STRCONV(s.str()));
1214 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1215 textclass.counters().step("bibitem");
1216 int number = textclass.counters().value("bibitem");
1217 if (par->bibitem()) {
1218 par->bibitem()->setCounter(number);
1219 par->params().labelString(layout->labelstring());
1221 // In biblio should't be following counters but...
1223 string s = layout->labelstring();
1225 // the caption hack:
1226 if (layout->labeltype == LABEL_SENSITIVE) {
1227 Paragraph * tmppar = par;
1230 while (tmppar && tmppar->inInset()
1231 // the single '=' is intended below
1232 && (in = tmppar->inInset()->owner())) {
1233 if (in->lyxCode() == Inset::FLOAT_CODE ||
1234 in->lyxCode() == Inset::WRAP_CODE) {
1238 tmppar = in->parOwner();
1244 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1246 textclass.counters().step(fl.type());
1248 // Doesn't work... yet.
1249 #warning use boost.format
1250 #if USE_BOOST_FORMAT
1251 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1252 // s << boost::format(_("%1$s %1$d:")
1254 // % buf->counters().value(fl.name());
1257 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1258 o << fl.name() << " #:";
1259 s = STRCONV(o.str());
1262 // par->SetLayout(0);
1263 // s = layout->labelstring;
1264 s = _("Senseless: ");
1267 par->params().labelString(s);
1269 // reset the enumeration counter. They are always reset
1270 // when there is any other layout between
1271 // Just fall-through between the cases so that all
1272 // enum counters deeper than enumdepth is also reset.
1273 switch (par->enumdepth) {
1275 textclass.counters().reset("enumi");
1277 textclass.counters().reset("enumii");
1279 textclass.counters().reset("enumiii");
1281 textclass.counters().reset("enumiv");
1287 // Updates all counters. Paragraphs with changed label string will be rebroken
1288 void LyXText::updateCounters()
1290 Row * row = firstRow();
1291 Paragraph * par = row->par();
1293 // CHECK if this is really needed. (Lgb)
1294 bv()->buffer()->params.getLyXTextClass().counters().reset();
1297 while (row->par() != par)
1300 string const oldLabel = par->params().labelString();
1302 // setCounter can potentially change the labelString.
1303 setCounter(bv()->buffer(), par);
1305 string const & newLabel = par->params().labelString();
1307 if (oldLabel.empty() && !newLabel.empty()) {
1308 removeParagraph(row);
1309 appendParagraph(row);
1317 void LyXText::insertInset(Inset * inset)
1319 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1321 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1323 cursor.par()->insertInset(cursor.pos(), inset);
1324 // Just to rebreak and refresh correctly.
1325 // The character will not be inserted a second time
1326 insertChar(Paragraph::META_INSET);
1327 // If we enter a highly editable inset the cursor should be to before
1328 // the inset. This couldn't happen before as Undo was not handled inside
1329 // inset now after the Undo LyX tries to call inset->Edit(...) again
1330 // and cannot do this as the cursor is behind the inset and GetInset
1331 // does not return the inset!
1332 if (isHighlyEditableInset(inset)) {
1339 void LyXText::copyEnvironmentType()
1341 copylayouttype = cursor.par()->layout()->name();
1345 void LyXText::pasteEnvironmentType()
1347 // do nothing if there has been no previous copyEnvironmentType()
1348 if (!copylayouttype.empty())
1349 setLayout(copylayouttype);
1353 void LyXText::cutSelection(bool doclear, bool realcut)
1355 // Stuff what we got on the clipboard. Even if there is no selection.
1357 // There is a problem with having the stuffing here in that the
1358 // larger the selection the slower LyX will get. This can be
1359 // solved by running the line below only when the selection has
1360 // finished. The solution used currently just works, to make it
1361 // faster we need to be more clever and probably also have more
1362 // calls to stuffClipboard. (Lgb)
1363 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1365 // This doesn't make sense, if there is no selection
1366 if (!selection.set())
1369 // OK, we have a selection. This is always between selection.start
1370 // and selection.end
1372 // make sure that the depth behind the selection are restored, too
1373 Paragraph * endpar = selection.end.par()->next();
1374 Paragraph * undoendpar = endpar;
1376 if (endpar && endpar->getDepth()) {
1377 while (endpar && endpar->getDepth()) {
1378 endpar = endpar->next();
1379 undoendpar = endpar;
1381 } else if (endpar) {
1382 endpar = endpar->next(); // because of parindents etc.
1385 setUndo(bv(), Undo::DELETE,
1386 selection.start.par(), undoendpar);
1388 // there are two cases: cut only within one paragraph or
1389 // more than one paragraph
1390 if (selection.start.par() == selection.end.par()) {
1391 // only within one paragraph
1392 endpar = selection.end.par();
1393 int pos = selection.end.pos();
1394 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1395 selection.start.pos(), pos,
1396 bv()->buffer()->params.textclass,
1398 selection.end.pos(pos);
1400 endpar = selection.end.par();
1401 int pos = selection.end.pos();
1402 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1403 selection.start.pos(), pos,
1404 bv()->buffer()->params.textclass,
1407 selection.end.par(endpar);
1408 selection.end.pos(pos);
1409 cursor.pos(selection.end.pos());
1411 endpar = endpar->next();
1413 // sometimes necessary
1415 selection.start.par()->stripLeadingSpaces();
1417 redoParagraphs(selection.start, endpar);
1419 // cutSelection can invalidate the cursor so we need to set
1421 // we prefer the end for when tracking changes
1422 cursor = selection.end;
1424 // need a valid cursor. (Lgb)
1427 setCursor(cursor.par(), cursor.pos());
1428 selection.cursor = cursor;
1433 void LyXText::copySelection()
1435 // stuff the selection onto the X clipboard, from an explicit copy request
1436 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1438 // this doesnt make sense, if there is no selection
1439 if (!selection.set())
1442 // ok we have a selection. This is always between selection.start
1443 // and sel_end cursor
1445 // copy behind a space if there is one
1446 while (selection.start.par()->size() > selection.start.pos()
1447 && selection.start.par()->isLineSeparator(selection.start.pos())
1448 && (selection.start.par() != selection.end.par()
1449 || selection.start.pos() < selection.end.pos()))
1450 selection.start.pos(selection.start.pos() + 1);
1452 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1453 selection.start.pos(), selection.end.pos(),
1454 bv()->buffer()->params.textclass);
1458 void LyXText::pasteSelection()
1460 // this does not make sense, if there is nothing to paste
1461 if (!CutAndPaste::checkPastePossible())
1464 setUndo(bv(), Undo::INSERT,
1465 cursor.par(), cursor.par()->next());
1468 Paragraph * actpar = cursor.par();
1469 int pos = cursor.pos();
1471 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1472 bv()->buffer()->params.textclass);
1474 redoParagraphs(cursor, endpar);
1476 setCursor(cursor.par(), cursor.pos());
1479 selection.cursor = cursor;
1480 setCursor(actpar, pos);
1486 void LyXText::setSelectionRange(lyx::pos_type length)
1491 selection.cursor = cursor;
1498 // simple replacing. The font of the first selected character is used
1499 void LyXText::replaceSelectionWithString(string const & str)
1501 setCursorParUndo(bv());
1504 if (!selection.set()) { // create a dummy selection
1505 selection.end = cursor;
1506 selection.start = cursor;
1509 // Get font setting before we cut
1510 pos_type pos = selection.end.pos();
1511 LyXFont const font = selection.start.par()
1512 ->getFontSettings(bv()->buffer()->params,
1513 selection.start.pos());
1515 // Insert the new string
1516 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1517 selection.end.par()->insertChar(pos, (*cit), font);
1521 // Cut the selection
1522 cutSelection(true, false);
1528 // needed to insert the selection
1529 void LyXText::insertStringAsLines(string const & str)
1531 Paragraph * par = cursor.par();
1532 pos_type pos = cursor.pos();
1533 Paragraph * endpar = cursor.par()->next();
1535 setCursorParUndo(bv());
1537 // only to be sure, should not be neccessary
1540 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1542 redoParagraphs(cursor, endpar);
1543 setCursor(cursor.par(), cursor.pos());
1544 selection.cursor = cursor;
1545 setCursor(par, pos);
1550 // turns double-CR to single CR, others where converted into one
1551 // blank. Then InsertStringAsLines is called
1552 void LyXText::insertStringAsParagraphs(string const & str)
1554 string linestr(str);
1555 bool newline_inserted = false;
1556 for (string::size_type i = 0; i < linestr.length(); ++i) {
1557 if (linestr[i] == '\n') {
1558 if (newline_inserted) {
1559 // we know that \r will be ignored by
1560 // InsertStringA. Of course, it is a dirty
1561 // trick, but it works...
1562 linestr[i - 1] = '\r';
1566 newline_inserted = true;
1568 } else if (IsPrintable(linestr[i])) {
1569 newline_inserted = false;
1572 insertStringAsLines(linestr);
1576 void LyXText::checkParagraph(Paragraph * par,
1579 LyXCursor tmpcursor;
1583 Row * row = getRow(par, pos, y);
1585 // is there a break one row above
1586 if (row->previous() && row->previous()->par() == row->par()) {
1587 z = rowBreakPoint(*row->previous());
1588 if (z >= row->pos()) {
1589 // set the dimensions of the row above
1590 y -= row->previous()->height();
1593 breakAgain(row->previous());
1595 // set the cursor again. Otherwise
1596 // dangling pointers are possible
1597 setCursor(cursor.par(), cursor.pos(),
1598 false, cursor.boundary());
1599 selection.cursor = cursor;
1604 int const tmpheight = row->height();
1605 pos_type const tmplast = row->lastPos();
1608 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1609 postRowPaint(row, y);
1614 // check the special right address boxes
1615 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1622 redoDrawingOfParagraph(tmpcursor);
1625 // set the cursor again. Otherwise dangling pointers are possible
1626 // also set the selection
1628 if (selection.set()) {
1630 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1631 false, selection.cursor.boundary());
1632 selection.cursor = cursor;
1633 setCursorIntern(selection.start.par(),
1634 selection.start.pos(),
1635 false, selection.start.boundary());
1636 selection.start = cursor;
1637 setCursorIntern(selection.end.par(),
1638 selection.end.pos(),
1639 false, selection.end.boundary());
1640 selection.end = cursor;
1641 setCursorIntern(last_sel_cursor.par(),
1642 last_sel_cursor.pos(),
1643 false, last_sel_cursor.boundary());
1644 last_sel_cursor = cursor;
1647 setCursorIntern(cursor.par(), cursor.pos(),
1648 false, cursor.boundary());
1652 // returns false if inset wasn't found
1653 bool LyXText::updateInset(Inset * inset)
1655 // first check the current paragraph
1656 int pos = cursor.par()->getPositionOfInset(inset);
1658 checkParagraph(cursor.par(), pos);
1662 // check every paragraph
1664 Paragraph * par = ownerParagraph();
1666 pos = par->getPositionOfInset(inset);
1668 checkParagraph(par, pos);
1678 bool LyXText::setCursor(Paragraph * par,
1680 bool setfont, bool boundary)
1682 LyXCursor old_cursor = cursor;
1683 setCursorIntern(par, pos, setfont, boundary);
1684 return deleteEmptyParagraphMechanism(old_cursor);
1688 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1689 pos_type pos, bool boundary)
1695 cur.boundary(boundary);
1697 // get the cursor y position in text
1699 Row * row = getRow(par, pos, y);
1700 Row * old_row = row;
1702 // if we are before the first char of this row and are still in the
1703 // same paragraph and there is a previous row then put the cursor on
1704 // the end of the previous row
1705 cur.iy(y + row->baseline());
1707 if (row->previous() && pos &&
1708 row->previous()->par() == row->par() &&
1709 pos < par->size() &&
1710 par->getChar(pos) == Paragraph::META_INSET &&
1711 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1713 row = row->previous();
1718 // y is now the beginning of the cursor row
1719 y += row->baseline();
1720 // y is now the cursor baseline
1723 pos_type last = old_row->lastPrintablePos();
1725 // None of these should happen, but we're scaredy-cats
1726 if (pos > par->size()) {
1727 lyxerr << "dont like 1 please report" << endl;
1730 } else if (pos > last + 1) {
1731 lyxerr << "dont like 2 please report" << endl;
1732 // This shouldn't happen.
1735 } else if (pos < row->pos()) {
1736 lyxerr << "dont like 3 please report" << endl;
1741 // now get the cursors x position
1742 float x = getCursorX(row, pos, last, boundary);
1745 if (old_row != row) {
1746 x = getCursorX(old_row, pos, last, boundary);
1750 //if the cursor is in a visible row, anchor to it
1752 if (topy < y && y < topy + bv()->workHeight())
1757 float LyXText::getCursorX(Row * row,
1758 pos_type pos, pos_type last, bool boundary) const
1760 pos_type cursor_vpos = 0;
1762 float fill_separator;
1764 float fill_label_hfill;
1765 // This call HAS to be here because of the BidiTables!!!
1766 prepareToPrint(row, x, fill_separator, fill_hfill,
1769 if (last < row->pos())
1770 cursor_vpos = row->pos();
1771 else if (pos > last && !boundary)
1772 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1773 ? row->pos() : last + 1;
1774 else if (pos > row->pos() &&
1775 (pos > last || boundary))
1776 /// Place cursor after char at (logical) position pos - 1
1777 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1778 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1780 /// Place cursor before char at (logical) position pos
1781 cursor_vpos = (bidi_level(pos) % 2 == 0)
1782 ? log2vis(pos) : log2vis(pos) + 1;
1784 pos_type body_pos = row->par()->beginningOfBody();
1785 if ((body_pos > 0) &&
1786 ((body_pos-1 > last) ||
1787 !row->par()->isLineSeparator(body_pos - 1)))
1790 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1791 pos_type pos = vis2log(vpos);
1792 if (body_pos > 0 && pos == body_pos - 1) {
1793 x += fill_label_hfill +
1794 font_metrics::width(
1795 row->par()->layout()->labelsep,
1796 getLabelFont(bv()->buffer(),
1798 if (row->par()->isLineSeparator(body_pos - 1))
1800 row->par(), body_pos - 1);
1802 if (row->hfillExpansion(pos)) {
1803 x += singleWidth(row->par(), pos);
1804 if (pos >= body_pos)
1807 x += fill_label_hfill;
1808 } else if (row->par()->isSeparator(pos)) {
1809 x += singleWidth(row->par(), pos);
1810 if (pos >= body_pos)
1811 x += fill_separator;
1813 x += singleWidth(row->par(), pos);
1819 void LyXText::setCursorIntern(Paragraph * par,
1820 pos_type pos, bool setfont, bool boundary)
1822 InsetText * it = static_cast<InsetText *>(par->inInset());
1824 if (it != inset_owner) {
1825 lyxerr[Debug::INSETS] << "InsetText is " << it
1827 << "inset_owner is "
1828 << inset_owner << endl;
1829 #ifdef WITH_WARNINGS
1830 #warning I believe this code is wrong. (Lgb)
1831 #warning Jürgen, have a look at this. (Lgb)
1832 #warning Hmmm, I guess you are right but we
1833 #warning should verify when this is needed
1835 // Jürgen, would you like to have a look?
1836 // I guess we need to move the outer cursor
1837 // and open and lock the inset (bla bla bla)
1838 // stuff I don't know... so can you have a look?
1840 // I moved the lyxerr stuff in here so we can see if
1841 // this is actually really needed and where!
1843 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1848 setCursor(cursor, par, pos, boundary);
1854 void LyXText::setCurrentFont()
1856 pos_type pos = cursor.pos();
1857 if (cursor.boundary() && pos > 0)
1861 if (pos == cursor.par()->size())
1863 else // potentional bug... BUG (Lgb)
1864 if (cursor.par()->isSeparator(pos)) {
1865 if (pos > cursor.row()->pos() &&
1866 bidi_level(pos) % 2 ==
1867 bidi_level(pos - 1) % 2)
1869 else if (pos + 1 < cursor.par()->size())
1875 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1876 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1878 if (cursor.pos() == cursor.par()->size() &&
1879 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1880 !cursor.boundary()) {
1881 Language const * lang =
1882 cursor.par()->getParLanguage(bv()->buffer()->params);
1883 current_font.setLanguage(lang);
1884 current_font.setNumber(LyXFont::OFF);
1885 real_current_font.setLanguage(lang);
1886 real_current_font.setNumber(LyXFont::OFF);
1891 // returns the column near the specified x-coordinate of the row
1892 // x is set to the real beginning of this column
1894 LyXText::getColumnNearX(Row * row, int & x,
1895 bool & boundary) const
1898 float fill_separator;
1900 float fill_label_hfill;
1902 prepareToPrint(row, tmpx, fill_separator,
1903 fill_hfill, fill_label_hfill);
1905 pos_type vc = row->pos();
1906 pos_type last = row->lastPrintablePos();
1909 LyXLayout_ptr const & layout = row->par()->layout();
1911 bool left_side = false;
1913 pos_type body_pos = row->par()->beginningOfBody();
1914 float last_tmpx = tmpx;
1917 (body_pos - 1 > last ||
1918 !row->par()->isLineSeparator(body_pos - 1)))
1921 // check for empty row
1922 if (!row->par()->size()) {
1927 while (vc <= last && tmpx <= x) {
1930 if (body_pos > 0 && c == body_pos-1) {
1931 tmpx += fill_label_hfill +
1932 font_metrics::width(layout->labelsep,
1933 getLabelFont(bv()->buffer(), row->par()));
1934 if (row->par()->isLineSeparator(body_pos - 1))
1935 tmpx -= singleWidth(row->par(), body_pos-1);
1938 if (row->hfillExpansion(c)) {
1939 tmpx += singleWidth(row->par(), c);
1943 tmpx += fill_label_hfill;
1944 } else if (row->par()->isSeparator(c)) {
1945 tmpx += singleWidth(row->par(), c);
1947 tmpx+= fill_separator;
1949 tmpx += singleWidth(row->par(), c);
1954 if ((tmpx + last_tmpx) / 2 > x) {
1959 if (vc > last + 1) // This shouldn't happen.
1963 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
1964 // some speedup if rtl_support=false
1965 && (!row->next() || row->next()->par() != row->par());
1966 bool const rtl = (lastrow)
1967 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
1968 : false; // If lastrow is false, we don't need to compute
1969 // the value of rtl.
1972 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
1973 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1975 else if (vc == row->pos()) {
1977 if (bidi_level(c) % 2 == 1)
1980 c = vis2log(vc - 1);
1981 bool const rtl = (bidi_level(c) % 2 == 1);
1982 if (left_side == rtl) {
1984 boundary = isBoundary(bv()->buffer(), row->par(), c);
1988 if (row->pos() <= last && c > last
1989 && row->par()->isNewline(last)) {
1990 if (bidi_level(last) % 2 == 0)
1991 tmpx -= singleWidth(row->par(), last);
1993 tmpx += singleWidth(row->par(), last);
2003 void LyXText::setCursorFromCoordinates(int x, int y)
2005 LyXCursor old_cursor = cursor;
2007 setCursorFromCoordinates(cursor, x, y);
2009 deleteEmptyParagraphMechanism(old_cursor);
2016 * return true if the cursor given is at the end of a row,
2017 * and the next row is filled by an inset that spans an entire
2020 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2023 Row const & next = *row.next();
2025 if (next.pos() != cur.pos() || next.par() != cur.par())
2027 if (!cur.par()->isInset(cur.pos()))
2029 Inset const * inset = cur.par()->getInset(cur.pos());
2030 if (inset->needFullRow() || inset->display())
2037 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2040 // Get the row first.
2042 Row * row = getRowNearY(y);
2044 pos_type const column = getColumnNearX(row, x, bound);
2045 cur.par(row->par());
2046 cur.pos(row->pos() + column);
2048 cur.y(y + row->baseline());
2051 if (beforeFullRowInset(*row, cur)) {
2052 pos_type last = row->lastPrintablePos();
2053 float x = getCursorX(row->next(), cur.pos(), last, bound);
2055 cur.iy(y + row->height() + row->next()->baseline());
2056 cur.irow(row->next());
2062 cur.boundary(bound);
2066 void LyXText::cursorLeft(bool internal)
2068 if (cursor.pos() > 0) {
2069 bool boundary = cursor.boundary();
2070 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2071 if (!internal && !boundary &&
2072 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2073 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2074 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2075 Paragraph * par = cursor.par()->previous();
2076 setCursor(par, par->size());
2081 void LyXText::cursorRight(bool internal)
2083 if (!internal && cursor.boundary() &&
2084 !cursor.par()->isNewline(cursor.pos()))
2085 setCursor(cursor.par(), cursor.pos(), true, false);
2086 else if (cursor.pos() < cursor.par()->size()) {
2087 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2089 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2090 setCursor(cursor.par(), cursor.pos(), true, true);
2091 } else if (cursor.par()->next())
2092 setCursor(cursor.par()->next(), 0);
2096 void LyXText::cursorUp(bool selecting)
2099 int x = cursor.x_fix();
2100 int y = cursor.y() - cursor.row()->baseline() - 1;
2101 setCursorFromCoordinates(x, y);
2104 int y1 = cursor.iy() - topy;
2107 Inset * inset_hit = checkInsetHit(x, y1);
2108 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2109 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2113 setCursorFromCoordinates(bv(), cursor.x_fix(),
2114 cursor.y() - cursor.row()->baseline() - 1);
2119 void LyXText::cursorDown(bool selecting)
2122 int x = cursor.x_fix();
2123 int y = cursor.y() - cursor.row()->baseline() +
2124 cursor.row()->height() + 1;
2125 setCursorFromCoordinates(x, y);
2126 if (!selecting && cursor.row() == cursor.irow()) {
2128 int y1 = cursor.iy() - topy;
2131 Inset * inset_hit = checkInsetHit(x, y1);
2132 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2133 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2137 setCursorFromCoordinates(bv(), cursor.x_fix(),
2138 cursor.y() - cursor.row()->baseline()
2139 + cursor.row()->height() + 1);
2144 void LyXText::cursorUpParagraph()
2146 if (cursor.pos() > 0) {
2147 setCursor(cursor.par(), 0);
2149 else if (cursor.par()->previous()) {
2150 setCursor(cursor.par()->previous(), 0);
2155 void LyXText::cursorDownParagraph()
2157 if (cursor.par()->next()) {
2158 setCursor(cursor.par()->next(), 0);
2160 setCursor(cursor.par(), cursor.par()->size());
2164 // fix the cursor `cur' after a characters has been deleted at `where'
2165 // position. Called by deleteEmptyParagraphMechanism
2166 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2167 LyXCursor const & where)
2169 // if cursor is not in the paragraph where the delete occured,
2171 if (cur.par() != where.par())
2174 // if cursor position is after the place where the delete occured,
2176 if (cur.pos() > where.pos())
2177 cur.pos(cur.pos()-1);
2179 // check also if we don't want to set the cursor on a spot behind the
2180 // pagragraph because we erased the last character.
2181 if (cur.pos() > cur.par()->size())
2182 cur.pos(cur.par()->size());
2184 // recompute row et al. for this cursor
2185 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2189 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2191 // Would be wrong to delete anything if we have a selection.
2192 if (selection.set())
2195 // We allow all kinds of "mumbo-jumbo" when freespacing.
2196 if (old_cursor.par()->layout()->free_spacing
2197 || old_cursor.par()->isFreeSpacing()) {
2201 /* Ok I'll put some comments here about what is missing.
2202 I have fixed BackSpace (and thus Delete) to not delete
2203 double-spaces automagically. I have also changed Cut,
2204 Copy and Paste to hopefully do some sensible things.
2205 There are still some small problems that can lead to
2206 double spaces stored in the document file or space at
2207 the beginning of paragraphs. This happens if you have
2208 the cursor betwenn to spaces and then save. Or if you
2209 cut and paste and the selection have a space at the
2210 beginning and then save right after the paste. I am
2211 sure none of these are very hard to fix, but I will
2212 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2213 that I can get some feedback. (Lgb)
2216 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2217 // delete the LineSeparator.
2220 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2221 // delete the LineSeparator.
2224 // If the pos around the old_cursor were spaces, delete one of them.
2225 if (old_cursor.par() != cursor.par()
2226 || old_cursor.pos() != cursor.pos()) {
2227 // Only if the cursor has really moved
2229 if (old_cursor.pos() > 0
2230 && old_cursor.pos() < old_cursor.par()->size()
2231 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2232 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2233 old_cursor.par()->erase(old_cursor.pos() - 1);
2234 redoParagraphs(old_cursor, old_cursor.par()->next());
2236 #ifdef WITH_WARNINGS
2237 #warning This will not work anymore when we have multiple views of the same buffer
2238 // In this case, we will have to correct also the cursors held by
2239 // other bufferviews. It will probably be easier to do that in a more
2240 // automated way in LyXCursor code. (JMarc 26/09/2001)
2242 // correct all cursors held by the LyXText
2243 fixCursorAfterDelete(cursor, old_cursor);
2244 fixCursorAfterDelete(selection.cursor,
2246 fixCursorAfterDelete(selection.start,
2248 fixCursorAfterDelete(selection.end, old_cursor);
2249 fixCursorAfterDelete(last_sel_cursor,
2251 fixCursorAfterDelete(toggle_cursor, old_cursor);
2252 fixCursorAfterDelete(toggle_end_cursor,
2258 // don't delete anything if this is the ONLY paragraph!
2259 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2262 // Do not delete empty paragraphs with keepempty set.
2263 if (old_cursor.par()->layout()->keepempty)
2266 // only do our magic if we changed paragraph
2267 if (old_cursor.par() == cursor.par())
2270 // record if we have deleted a paragraph
2271 // we can't possibly have deleted a paragraph before this point
2272 bool deleted = false;
2274 if ((old_cursor.par()->empty()
2275 || (old_cursor.par()->size() == 1
2276 && old_cursor.par()->isLineSeparator(0)))) {
2277 // ok, we will delete anything
2278 LyXCursor tmpcursor;
2282 if (old_cursor.row()->previous()) {
2283 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2284 - old_cursor.row()->previous()->height());
2286 cursor = old_cursor; // that undo can restore the right cursor position
2287 Paragraph * endpar = old_cursor.par()->next();
2288 if (endpar && endpar->getDepth()) {
2289 while (endpar && endpar->getDepth()) {
2290 endpar = endpar->next();
2293 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2297 removeRow(old_cursor.row());
2298 if (ownerParagraph() == old_cursor.par()) {
2299 ownerParagraph(ownerParagraph()->next());
2302 delete old_cursor.par();
2304 /* Breakagain the next par. Needed because of
2305 * the parindent that can occur or dissappear.
2306 * The next row can change its height, if
2307 * there is another layout before */
2309 if (refresh_row->next()) {
2310 breakAgain(refresh_row->next());
2313 setHeightOfRow(refresh_row);
2316 Row * nextrow = old_cursor.row()->next();
2317 const_cast<LyXText *>(this)->postPaint(
2318 old_cursor.y() - old_cursor.row()->baseline());
2321 cursor = old_cursor; // that undo can restore the right cursor position
2322 Paragraph * endpar = old_cursor.par()->next();
2323 if (endpar && endpar->getDepth()) {
2324 while (endpar && endpar->getDepth()) {
2325 endpar = endpar->next();
2328 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2332 removeRow(old_cursor.row());
2334 if (ownerParagraph() == old_cursor.par()) {
2335 ownerParagraph(ownerParagraph()->next());
2338 delete old_cursor.par();
2340 /* Breakagain the next par. Needed because of
2341 the parindent that can occur or dissappear.
2342 The next row can change its height, if
2343 there is another layout before */
2345 breakAgain(nextrow);
2351 setCursorIntern(cursor.par(), cursor.pos());
2353 if (selection.cursor.par() == old_cursor.par()
2354 && selection.cursor.pos() == old_cursor.pos()) {
2355 // correct selection
2356 selection.cursor = cursor;
2360 if (old_cursor.par()->stripLeadingSpaces()) {
2361 redoParagraphs(old_cursor,
2362 old_cursor.par()->next());
2364 setCursorIntern(cursor.par(), cursor.pos());
2365 selection.cursor = cursor;
2372 Paragraph * LyXText::ownerParagraph() const
2375 return inset_owner->paragraph();
2377 return &*(bv_owner->buffer()->paragraphs.begin());
2381 void LyXText::ownerParagraph(Paragraph * p) const
2384 inset_owner->paragraph(p);
2386 bv_owner->buffer()->paragraphs.set(p);
2391 void LyXText::ownerParagraph(int id, Paragraph * p) const
2393 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2394 if (op && op->inInset()) {
2395 static_cast<InsetText *>(op->inInset())->paragraph(p);
2402 LyXText::refresh_status LyXText::refreshStatus() const
2404 return refresh_status_;
2408 void LyXText::clearPaint()
2410 refresh_status_ = REFRESH_NONE;
2416 void LyXText::postPaint(int start_y)
2418 refresh_status old = refresh_status_;
2420 refresh_status_ = REFRESH_AREA;
2423 if (old != REFRESH_NONE && refresh_y < start_y)
2426 refresh_y = start_y;
2431 // We are an inset's lyxtext. Tell the top-level lyxtext
2432 // it needs to update the row we're in.
2433 LyXText * t = bv()->text;
2434 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2438 // FIXME: we should probably remove this y parameter,
2439 // make refresh_y be 0, and use row->y etc.
2440 void LyXText::postRowPaint(Row * row, int start_y)
2442 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2443 refresh_status_ = REFRESH_AREA;
2446 refresh_y = start_y;
2449 if (refresh_status_ == REFRESH_AREA)
2452 refresh_status_ = REFRESH_ROW;
2458 // We are an inset's lyxtext. Tell the top-level lyxtext
2459 // it needs to update the row we're in.
2460 LyXText * t = bv()->text;
2461 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2465 bool LyXText::isInInset() const
2467 // Sub-level has non-null bv owner and
2468 // non-null inset owner.
2469 return inset_owner != 0 && bv_owner != 0;
2473 int defaultRowHeight()
2475 LyXFont const font(LyXFont::ALL_SANE);
2476 return int(font_metrics::maxAscent(font)
2477 + font_metrics::maxDescent(font) * 1.5);