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),
57 bv_owner(bv), firstrow(0), lastrow(0)
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), need_break_row(0),
66 bv_owner(bv), firstrow(0), lastrow(0)
72 void LyXText::init(BufferView * bview, bool reinit)
75 // Delete all rows, this does not touch the paragraphs!
76 Row * tmprow = firstrow;
78 tmprow = firstrow->next();
86 copylayouttype.erase();
92 Paragraph * par = ownerParagraph();
93 current_font = getFont(bview->buffer(), par, 0);
96 insertParagraph(par, lastrow);
99 setCursorIntern(firstrow->par(), 0);
100 selection.cursor = cursor;
108 // Delete all rows, this does not touch the paragraphs!
109 Row * tmprow = firstrow;
111 tmprow = firstrow->next();
120 LyXFont const realizeFont(LyXFont const & font,
124 LyXTextClass const & tclass = buf->params.getLyXTextClass();
125 LyXFont tmpfont(font);
126 Paragraph::depth_type par_depth = par->getDepth();
128 // Resolve against environment font information
129 while (par && par_depth && !tmpfont.resolved()) {
130 par = par->outerHook();
132 tmpfont.realize(par->layout()->font);
133 par_depth = par->getDepth();
137 tmpfont.realize(tclass.defaultfont());
145 // Gets the fully instantiated font at a given position in a paragraph
146 // Basically the same routine as Paragraph::getFont() in paragraph.C.
147 // The difference is that this one is used for displaying, and thus we
148 // are allowed to make cosmetic improvements. For instance make footnotes
150 // If position is -1, we get the layout font of the paragraph.
151 // If position is -2, we get the font of the manual label of the paragraph.
152 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
155 lyx::Assert(pos >= 0);
157 LyXLayout_ptr const & layout = par->layout();
159 // We specialize the 95% common case:
160 if (!par->getDepth()) {
161 if (layout->labeltype == LABEL_MANUAL
162 && pos < par->beginningOfBody()) {
164 LyXFont f = par->getFontSettings(buf->params, pos);
166 par->inInset()->getDrawFont(f);
167 return f.realize(layout->reslabelfont);
169 LyXFont f = par->getFontSettings(buf->params, pos);
171 par->inInset()->getDrawFont(f);
172 return f.realize(layout->resfont);
176 // The uncommon case need not be optimized as much
180 if (pos < par->beginningOfBody()) {
182 layoutfont = layout->labelfont;
185 layoutfont = layout->font;
188 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
189 tmpfont.realize(layoutfont);
192 par->inInset()->getDrawFont(tmpfont);
194 return realizeFont(tmpfont, buf, par);
198 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
200 LyXLayout_ptr const & layout = par->layout();
202 if (!par->getDepth()) {
203 return layout->resfont;
206 return realizeFont(layout->font, buf, par);
210 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
212 LyXLayout_ptr const & layout = par->layout();
214 if (!par->getDepth()) {
215 return layout->reslabelfont;
218 return realizeFont(layout->labelfont, buf, par);
222 void LyXText::setCharFont(Paragraph * par,
223 pos_type pos, LyXFont const & fnt,
226 Buffer const * buf = bv()->buffer();
227 LyXFont font = getFont(buf, par, pos);
228 font.update(fnt, buf->params.language, toggleall);
229 // Let the insets convert their font
230 if (par->isInset(pos)) {
231 Inset * inset = par->getInset(pos);
232 if (isEditableInset(inset)) {
233 UpdatableInset * uinset =
234 static_cast<UpdatableInset *>(inset);
235 uinset->setFont(bv(), fnt, toggleall, true);
239 // Plug thru to version below:
240 setCharFont(buf, par, pos, font);
244 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
245 pos_type pos, LyXFont const & fnt)
249 LyXTextClass const & tclass = buf->params.getLyXTextClass();
250 LyXLayout_ptr const & layout = par->layout();
252 // Get concrete layout font to reduce against
255 if (pos < par->beginningOfBody())
256 layoutfont = layout->labelfont;
258 layoutfont = layout->font;
260 // Realize against environment font information
261 if (par->getDepth()) {
262 Paragraph * tp = par;
263 while (!layoutfont.resolved() && tp && tp->getDepth()) {
264 tp = tp->outerHook();
266 layoutfont.realize(tp->layout()->font);
270 layoutfont.realize(tclass.defaultfont());
272 // Now, reduce font against full layout font
273 font.reduce(layoutfont);
275 par->setFont(pos, font);
279 // inserts a new row before the specified row, increments
280 // the touched counters
281 void LyXText::insertRow(Row * row, Paragraph * par,
284 Row * tmprow = new Row;
287 tmprow->next(firstrow);
290 tmprow->previous(row);
291 tmprow->next(row->next());
296 tmprow->next()->previous(tmprow);
298 if (tmprow->previous())
299 tmprow->previous()->next(tmprow);
310 // removes the row and reset the touched counters
311 void LyXText::removeRow(Row * row)
313 Row * row_prev = row->previous();
315 row->next()->previous(row_prev);
317 firstrow = row->next();
318 // lyx::Assert(firstrow);
320 row_prev->next(row->next());
322 if (row == lastrow) {
323 lyx::Assert(!row->next());
327 /* FIXME: when we cache the bview, this should just
328 * become a postPaint(), I think */
329 if (refresh_row == row) {
330 refresh_row = row_prev ? row_prev : row->next();
331 // what about refresh_y
334 if (anchor_row_ == row) {
336 anchor_row_ = row_prev;
337 anchor_row_offset_ = 0;
339 anchor_row_ = row->next();
340 anchor_row_offset_ -= row->height();
344 height -= row->height(); // the text becomes smaller
350 // remove all following rows of the paragraph of the specified row.
351 void LyXText::removeParagraph(Row * row)
353 Paragraph * tmppar = row->par();
357 while (row && row->par() == tmppar) {
358 tmprow = row->next();
365 // insert the specified paragraph behind the specified row
366 void LyXText::insertParagraph(Paragraph * par,
369 // insert a new row, starting at position 0
370 insertRow(row, par, 0);
372 // and now append the whole paragraph before the new row
375 appendParagraph(firstrow);
377 row->next()->height(0);
378 appendParagraph(row->next());
383 Inset * LyXText::getInset() const
385 if (cursor.pos() < cursor.par()->size()
386 && cursor.par()->isInset(cursor.pos())) {
387 return cursor.par()->getInset(cursor.pos());
393 void LyXText::toggleInset()
395 Inset * inset = getInset();
396 // is there an editable inset at cursor position?
397 if (!isEditableInset(inset)) {
398 // No, try to see if we are inside a collapsable inset
399 if (inset_owner && inset_owner->owner()
400 && inset_owner->owner()->isOpen()) {
401 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
402 inset_owner->owner()->close(bv());
403 bv()->getLyXText()->cursorRight(bv());
407 //bv()->owner()->message(inset->editMessage());
409 // do we want to keep this?? (JMarc)
410 if (!isHighlyEditableInset(inset))
411 setCursorParUndo(bv());
413 if (inset->isOpen()) {
419 bv()->updateInset(inset);
423 /* used in setlayout */
424 // Asger is not sure we want to do this...
425 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
428 LyXLayout_ptr const & layout = par.layout();
431 for (pos_type pos = 0; pos < par.size(); ++pos) {
432 if (pos < par.beginningOfBody())
433 layoutfont = layout->labelfont;
435 layoutfont = layout->font;
437 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
438 tmpfont.reduce(layoutfont);
439 par.setFont(pos, tmpfont);
444 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
445 LyXCursor & send_cur,
446 string const & layout)
448 Paragraph * endpar = send_cur.par()->next();
449 Paragraph * undoendpar = endpar;
451 if (endpar && endpar->getDepth()) {
452 while (endpar && endpar->getDepth()) {
453 endpar = endpar->next();
457 endpar = endpar->next(); // because of parindents etc.
460 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
462 // ok we have a selection. This is always between sstart_cur
463 // and sel_end cursor
465 Paragraph * par = sstart_cur.par();
466 Paragraph * epar = send_cur.par()->next();
468 LyXLayout_ptr const & lyxlayout =
469 bv()->buffer()->params.getLyXTextClass()[layout];
472 par->applyLayout(lyxlayout);
473 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
474 Paragraph * fppar = par;
475 fppar->params().spaceTop(lyxlayout->fill_top ?
476 VSpace(VSpace::VFILL)
477 : VSpace(VSpace::NONE));
478 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
479 VSpace(VSpace::VFILL)
480 : VSpace(VSpace::NONE));
481 if (lyxlayout->margintype == MARGIN_MANUAL)
482 par->setLabelWidthString(lyxlayout->labelstring());
485 } while (par != epar);
491 // set layout over selection and make a total rebreak of those paragraphs
492 void LyXText::setLayout(string const & layout)
494 LyXCursor tmpcursor = cursor; /* store the current cursor */
496 // if there is no selection just set the layout
497 // of the current paragraph */
498 if (!selection.set()) {
499 selection.start = cursor; // dummy selection
500 selection.end = cursor;
502 Paragraph * endpar = setLayout(cursor, selection.start,
503 selection.end, layout);
504 redoParagraphs(selection.start, endpar);
506 // we have to reset the selection, because the
507 // geometry could have changed
508 setCursor(selection.start.par(),
509 selection.start.pos(), false);
510 selection.cursor = cursor;
511 setCursor(selection.end.par(), selection.end.pos(), false);
515 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
519 // increment depth over selection and
520 // make a total rebreak of those paragraphs
521 void LyXText::incDepth()
523 // If there is no selection, just use the current paragraph
524 if (!selection.set()) {
525 selection.start = cursor; // dummy selection
526 selection.end = cursor;
529 // We end at the next paragraph with depth 0
530 Paragraph * endpar = selection.end.par()->next();
532 Paragraph * undoendpar = endpar;
534 if (endpar && endpar->getDepth()) {
535 while (endpar && endpar->getDepth()) {
536 endpar = endpar->next();
540 endpar = endpar->next(); // because of parindents etc.
543 setUndo(bv(), Undo::EDIT,
544 selection.start.par(), undoendpar);
546 LyXCursor tmpcursor = cursor; // store the current cursor
548 // ok we have a selection. This is always between sel_start_cursor
549 // and sel_end cursor
550 cursor = selection.start;
553 // NOTE: you can't change the depth of a bibliography entry
554 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
555 Paragraph * prev = cursor.par()->previous();
558 if (cursor.par()->getDepth()
559 < prev->getMaxDepthAfter()) {
560 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
564 if (cursor.par() == selection.end.par())
566 cursor.par(cursor.par()->next());
569 redoParagraphs(selection.start, endpar);
571 // we have to reset the selection, because the
572 // geometry could have changed
573 setCursor(selection.start.par(), selection.start.pos());
574 selection.cursor = cursor;
575 setCursor(selection.end.par(), selection.end.pos());
579 setCursor(tmpcursor.par(), tmpcursor.pos());
583 // decrement depth over selection and
584 // make a total rebreak of those paragraphs
585 void LyXText::decDepth()
587 // if there is no selection just set the layout
588 // of the current paragraph
589 if (!selection.set()) {
590 selection.start = cursor; // dummy selection
591 selection.end = cursor;
593 Paragraph * endpar = selection.end.par()->next();
594 Paragraph * undoendpar = endpar;
596 if (endpar && endpar->getDepth()) {
597 while (endpar && endpar->getDepth()) {
598 endpar = endpar->next();
602 endpar = endpar->next(); // because of parindents etc.
605 setUndo(bv(), Undo::EDIT,
606 selection.start.par(), undoendpar);
608 LyXCursor tmpcursor = cursor; // store the current cursor
610 // ok we have a selection. This is always between sel_start_cursor
611 // and sel_end cursor
612 cursor = selection.start;
615 if (cursor.par()->params().depth()) {
616 cursor.par()->params()
617 .depth(cursor.par()->params().depth() - 1);
619 if (cursor.par() == selection.end.par()) {
622 cursor.par(cursor.par()->next());
625 redoParagraphs(selection.start, endpar);
627 // we have to reset the selection, because the
628 // geometry could have changed
629 setCursor(selection.start.par(),
630 selection.start.pos());
631 selection.cursor = cursor;
632 setCursor(selection.end.par(), selection.end.pos());
636 setCursor(tmpcursor.par(), tmpcursor.pos());
640 // set font over selection and make a total rebreak of those paragraphs
641 void LyXText::setFont(LyXFont const & font, bool toggleall)
643 // if there is no selection just set the current_font
644 if (!selection.set()) {
645 // Determine basis font
647 if (cursor.pos() < cursor.par()->beginningOfBody()) {
648 layoutfont = getLabelFont(bv()->buffer(),
651 layoutfont = getLayoutFont(bv()->buffer(),
654 // Update current font
655 real_current_font.update(font,
656 bv()->buffer()->params.language,
659 // Reduce to implicit settings
660 current_font = real_current_font;
661 current_font.reduce(layoutfont);
662 // And resolve it completely
663 real_current_font.realize(layoutfont);
668 LyXCursor tmpcursor = cursor; // store the current cursor
670 // ok we have a selection. This is always between sel_start_cursor
671 // and sel_end cursor
673 setUndo(bv(), Undo::EDIT,
674 selection.start.par(), selection.end.par()->next());
676 cursor = selection.start;
677 while (cursor.par() != selection.end.par() ||
678 cursor.pos() < selection.end.pos())
680 if (cursor.pos() < cursor.par()->size()) {
681 // an open footnote should behave like a closed one
682 setCharFont(cursor.par(), cursor.pos(),
684 cursor.pos(cursor.pos() + 1);
687 cursor.par(cursor.par()->next());
692 redoParagraphs(selection.start, selection.end.par()->next());
694 // we have to reset the selection, because the
695 // geometry could have changed, but we keep
696 // it for user convenience
697 setCursor(selection.start.par(), selection.start.pos());
698 selection.cursor = cursor;
699 setCursor(selection.end.par(), selection.end.pos());
701 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
702 tmpcursor.boundary());
706 void LyXText::redoHeightOfParagraph()
708 Row * tmprow = cursor.row();
709 int y = cursor.y() - tmprow->baseline();
711 setHeightOfRow(tmprow);
713 while (tmprow->previous()
714 && tmprow->previous()->par() == tmprow->par()) {
715 tmprow = tmprow->previous();
716 y -= tmprow->height();
717 setHeightOfRow(tmprow);
722 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
726 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
728 Row * tmprow = cur.row();
730 int y = cur.y() - tmprow->baseline();
731 setHeightOfRow(tmprow);
733 while (tmprow->previous()
734 && tmprow->previous()->par() == tmprow->par()) {
735 tmprow = tmprow->previous();
736 y -= tmprow->height();
740 setCursor(cur.par(), cur.pos());
744 // deletes and inserts again all paragaphs between the cursor
745 // and the specified par
746 // This function is needed after SetLayout and SetFont etc.
747 void LyXText::redoParagraphs(LyXCursor const & cur,
748 Paragraph const * endpar)
751 Paragraph * tmppar = 0;
752 Paragraph * first_phys_par = 0;
754 Row * tmprow = cur.row();
756 int y = cur.y() - tmprow->baseline();
758 if (!tmprow->previous()) {
759 // a trick/hack for UNDO
760 // This is needed because in an UNDO/REDO we could have changed
761 // the ownerParagrah() so the paragraph inside the row is NOT
762 // my really first par anymore. Got it Lars ;) (Jug 20011206)
763 first_phys_par = ownerParagraph();
765 first_phys_par = tmprow->par();
766 while (tmprow->previous()
767 && tmprow->previous()->par() == first_phys_par)
769 tmprow = tmprow->previous();
770 y -= tmprow->height();
774 Row * prevrow = tmprow->previous();
778 tmppar = tmprow->next()->par();
781 while (tmprow->next() && tmppar != endpar) {
782 removeRow(tmprow->next());
783 if (tmprow->next()) {
784 tmppar = tmprow->next()->par();
790 // remove the first one
791 tmprow2 = tmprow; /* this is because tmprow->previous()
793 tmprow = tmprow->previous();
796 tmppar = first_phys_par;
800 insertParagraph(tmppar, tmprow);
804 while (tmprow->next()
805 && tmprow->next()->par() == tmppar) {
806 tmprow = tmprow->next();
808 tmppar = tmppar->next();
810 } while (tmppar && tmppar != endpar);
812 // this is because of layout changes
814 setHeightOfRow(prevrow);
815 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
817 setHeightOfRow(firstrow);
818 const_cast<LyXText *>(this)->postPaint(0);
821 if (tmprow && tmprow->next())
822 setHeightOfRow(tmprow->next());
827 void LyXText::fullRebreak()
833 if (need_break_row) {
834 breakAgain(need_break_row);
841 // important for the screen
844 // the cursor set functions have a special mechanism. When they
845 // realize, that you left an empty paragraph, they will delete it.
846 // They also delete the corresponding row
848 // need the selection cursor:
849 void LyXText::setSelection()
851 bool const lsel = selection.set();
853 if (!selection.set()) {
854 last_sel_cursor = selection.cursor;
855 selection.start = selection.cursor;
856 selection.end = selection.cursor;
861 // first the toggling area
862 if (cursor.y() < last_sel_cursor.y()
863 || (cursor.y() == last_sel_cursor.y()
864 && cursor.x() < last_sel_cursor.x())) {
865 toggle_end_cursor = last_sel_cursor;
866 toggle_cursor = cursor;
868 toggle_end_cursor = cursor;
869 toggle_cursor = last_sel_cursor;
872 last_sel_cursor = cursor;
874 // and now the whole selection
876 if (selection.cursor.par() == cursor.par())
877 if (selection.cursor.pos() < cursor.pos()) {
878 selection.end = cursor;
879 selection.start = selection.cursor;
881 selection.end = selection.cursor;
882 selection.start = cursor;
884 else if (selection.cursor.y() < cursor.y() ||
885 (selection.cursor.y() == cursor.y()
886 && selection.cursor.x() < cursor.x())) {
887 selection.end = cursor;
888 selection.start = selection.cursor;
891 selection.end = selection.cursor;
892 selection.start = cursor;
895 // a selection with no contents is not a selection
896 if (selection.start.par() == selection.end.par() &&
897 selection.start.pos() == selection.end.pos())
898 selection.set(false);
900 if (inset_owner && (selection.set() || lsel))
901 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
905 string const LyXText::selectionAsString(Buffer const * buffer,
908 if (!selection.set()) return string();
910 // should be const ...
911 Paragraph * startpar(selection.start.par());
912 Paragraph * endpar(selection.end.par());
913 pos_type const startpos(selection.start.pos());
914 pos_type const endpos(selection.end.pos());
916 if (startpar == endpar) {
917 return startpar->asString(buffer, startpos, endpos, label);
922 // First paragraph in selection
923 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
925 // The paragraphs in between (if any)
926 LyXCursor tmpcur(selection.start);
927 tmpcur.par(tmpcur.par()->next());
928 while (tmpcur.par() != endpar) {
929 result += tmpcur.par()->asString(buffer, 0,
930 tmpcur.par()->size(),
932 tmpcur.par(tmpcur.par()->next());
935 // Last paragraph in selection
936 result += endpar->asString(buffer, 0, endpos, label);
942 void LyXText::clearSelection()
944 selection.set(false);
945 selection.mark(false);
946 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
947 // reset this in the bv_owner!
948 if (bv_owner && bv_owner->text)
949 bv_owner->text->xsel_cache.set(false);
953 void LyXText::cursorHome()
955 setCursor(cursor.par(), cursor.row()->pos());
959 void LyXText::cursorEnd()
961 if (!cursor.row()->next()
962 || cursor.row()->next()->par() != cursor.row()->par()) {
963 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
965 if (!cursor.par()->empty() &&
966 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
967 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
968 setCursor(cursor.par(), cursor.row()->lastPos());
970 setCursor(cursor.par(),
971 cursor.row()->lastPos() + 1);
977 void LyXText::cursorTop()
979 while (cursor.par()->previous())
980 cursor.par(cursor.par()->previous());
981 setCursor(cursor.par(), 0);
985 void LyXText::cursorBottom()
987 while (cursor.par()->next())
988 cursor.par(cursor.par()->next());
989 setCursor(cursor.par(), cursor.par()->size());
993 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
995 // If the mask is completely neutral, tell user
996 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
997 // Could only happen with user style
998 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
1002 // Try implicit word selection
1003 // If there is a change in the language the implicit word selection
1005 LyXCursor resetCursor = cursor;
1006 bool implicitSelection = (font.language() == ignore_language
1007 && font.number() == LyXFont::IGNORE)
1008 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
1011 setFont(font, toggleall);
1013 // Implicit selections are cleared afterwards
1014 //and cursor is set to the original position.
1015 if (implicitSelection) {
1017 cursor = resetCursor;
1018 setCursor(cursor.par(), cursor.pos());
1019 selection.cursor = cursor;
1022 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
1026 string LyXText::getStringToIndex()
1028 // Try implicit word selection
1029 // If there is a change in the language the implicit word selection
1031 LyXCursor const reset_cursor = cursor;
1032 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
1035 if (!selection.set())
1036 bv()->owner()->message(_("Nothing to index!"));
1037 else if (selection.start.par() != selection.end.par())
1038 bv()->owner()->message(_("Cannot index more than one paragraph!"));
1040 idxstring = selectionAsString(bv()->buffer(), false);
1042 // Reset cursors to their original position.
1043 cursor = reset_cursor;
1044 setCursor(cursor.par(), cursor.pos());
1045 selection.cursor = cursor;
1047 // Clear the implicit selection.
1048 if (implicitSelection)
1055 // the DTP switches for paragraphs. LyX will store them in the first
1056 // physicla paragraph. When a paragraph is broken, the top settings rest,
1057 // the bottom settings are given to the new one. So I can make shure,
1058 // they do not duplicate themself and you cannnot make dirty things with
1061 void LyXText::setParagraph(bool line_top, bool line_bottom,
1062 bool pagebreak_top, bool pagebreak_bottom,
1063 VSpace const & space_top,
1064 VSpace const & space_bottom,
1065 Spacing const & spacing,
1067 string const & labelwidthstring,
1070 LyXCursor tmpcursor = cursor;
1071 if (!selection.set()) {
1072 selection.start = cursor;
1073 selection.end = cursor;
1076 // make sure that the depth behind the selection are restored, too
1077 Paragraph * endpar = selection.end.par()->next();
1078 Paragraph * undoendpar = endpar;
1080 if (endpar && endpar->getDepth()) {
1081 while (endpar && endpar->getDepth()) {
1082 endpar = endpar->next();
1083 undoendpar = endpar;
1087 // because of parindents etc.
1088 endpar = endpar->next();
1091 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1094 Paragraph * tmppar = selection.end.par();
1096 while (tmppar != selection.start.par()->previous()) {
1097 setCursor(tmppar, 0);
1098 postPaint(cursor.y() - cursor.row()->baseline());
1099 cursor.par()->params().lineTop(line_top);
1100 cursor.par()->params().lineBottom(line_bottom);
1101 cursor.par()->params().pagebreakTop(pagebreak_top);
1102 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1103 cursor.par()->params().spaceTop(space_top);
1104 cursor.par()->params().spaceBottom(space_bottom);
1105 cursor.par()->params().spacing(spacing);
1106 // does the layout allow the new alignment?
1107 LyXLayout_ptr const & layout = cursor.par()->layout();
1109 if (align == LYX_ALIGN_LAYOUT)
1110 align = layout->align;
1111 if (align & layout->alignpossible) {
1112 if (align == layout->align)
1113 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1115 cursor.par()->params().align(align);
1117 cursor.par()->setLabelWidthString(labelwidthstring);
1118 cursor.par()->params().noindent(noindent);
1119 tmppar = cursor.par()->previous();
1122 redoParagraphs(selection.start, endpar);
1125 setCursor(selection.start.par(), selection.start.pos());
1126 selection.cursor = cursor;
1127 setCursor(selection.end.par(), selection.end.pos());
1129 setCursor(tmpcursor.par(), tmpcursor.pos());
1131 bv()->updateInset(inset_owner);
1135 // set the counter of a paragraph. This includes the labels
1136 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1138 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1139 LyXLayout_ptr const & layout = par->layout();
1141 if (par->previous()) {
1143 par->params().appendix(par->previous()->params().appendix());
1144 if (!par->params().appendix() && par->params().startOfAppendix()) {
1145 par->params().appendix(true);
1146 textclass.counters().reset();
1148 par->enumdepth = par->previous()->enumdepth;
1149 par->itemdepth = par->previous()->itemdepth;
1151 par->params().appendix(par->params().startOfAppendix());
1156 /* Maybe we have to increment the enumeration depth.
1157 * BUT, enumeration in a footnote is considered in isolation from its
1158 * surrounding paragraph so don't increment if this is the
1159 * first line of the footnote
1160 * AND, bibliographies can't have their depth changed ie. they
1161 * are always of depth 0
1164 && par->previous()->getDepth() < par->getDepth()
1165 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1166 && par->enumdepth < 3
1167 && layout->labeltype != LABEL_BIBLIO) {
1171 // Maybe we have to decrement the enumeration depth, see note above
1173 && par->previous()->getDepth() > par->getDepth()
1174 && layout->labeltype != LABEL_BIBLIO) {
1175 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1178 if (!par->params().labelString().empty()) {
1179 par->params().labelString(string());
1182 if (layout->margintype == MARGIN_MANUAL) {
1183 if (par->params().labelWidthString().empty()) {
1184 par->setLabelWidthString(layout->labelstring());
1187 par->setLabelWidthString(string());
1190 // is it a layout that has an automatic label?
1191 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1192 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1196 if (i >= 0 && i <= buf->params.secnumdepth) {
1200 textclass.counters().step(layout->latexname());
1202 // Is there a label? Useful for Chapter layout
1203 if (!par->params().appendix()) {
1204 s << layout->labelstring();
1206 s << layout->labelstring_appendix();
1209 // Use of an integer is here less than elegant. For now.
1210 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1211 if (!par->params().appendix()) {
1212 numbertype = "sectioning";
1214 numbertype = "appendix";
1215 if (par->isRightToLeftPar(buf->params))
1216 langtype = "hebrew";
1221 s << textclass.counters()
1222 .numberLabel(layout->latexname(),
1223 numbertype, langtype, head);
1225 par->params().labelString(STRCONV(s.str()));
1227 // reset enum counters
1228 textclass.counters().reset("enum");
1229 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1230 textclass.counters().reset("enum");
1231 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1233 // Yes I know this is a really, really! bad solution
1235 string enumcounter("enum");
1237 switch (par->enumdepth) {
1246 enumcounter += "iv";
1249 // not a valid enumdepth...
1253 textclass.counters().step(enumcounter);
1255 s << textclass.counters()
1256 .numberLabel(enumcounter, "enumeration");
1257 par->params().labelString(STRCONV(s.str()));
1259 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1260 textclass.counters().step("bibitem");
1261 int number = textclass.counters().value("bibitem");
1262 if (par->bibitem()) {
1263 par->bibitem()->setCounter(number);
1264 par->params().labelString(layout->labelstring());
1266 // In biblio should't be following counters but...
1268 string s = layout->labelstring();
1270 // the caption hack:
1271 if (layout->labeltype == LABEL_SENSITIVE) {
1272 Paragraph * tmppar = par;
1275 while (tmppar && tmppar->inInset()
1276 // the single '=' is intended below
1277 && (in = tmppar->inInset()->owner())) {
1278 if (in->lyxCode() == Inset::FLOAT_CODE ||
1279 in->lyxCode() == Inset::WRAP_CODE) {
1283 tmppar = in->parOwner();
1289 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1291 textclass.counters().step(fl.type());
1293 // Doesn't work... yet.
1294 #warning use boost.format
1295 #if USE_BOOST_FORMAT
1296 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1297 // s << boost::format(_("%1$s %1$d:")
1299 // % buf->counters().value(fl.name());
1302 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1303 o << fl.name() << " #:";
1304 s = STRCONV(o.str());
1307 // par->SetLayout(0);
1308 // s = layout->labelstring;
1309 s = _("Senseless: ");
1312 par->params().labelString(s);
1314 // reset the enumeration counter. They are always reset
1315 // when there is any other layout between
1316 // Just fall-through between the cases so that all
1317 // enum counters deeper than enumdepth is also reset.
1318 switch (par->enumdepth) {
1320 textclass.counters().reset("enumi");
1322 textclass.counters().reset("enumii");
1324 textclass.counters().reset("enumiii");
1326 textclass.counters().reset("enumiv");
1332 // Updates all counters. Paragraphs with changed label string will be rebroken
1333 void LyXText::updateCounters()
1335 Row * row = firstrow;
1336 Paragraph * par = row->par();
1338 // CHECK if this is really needed. (Lgb)
1339 bv()->buffer()->params.getLyXTextClass().counters().reset();
1342 while (row->par() != par)
1345 string const oldLabel = par->params().labelString();
1347 // setCounter can potentially change the labelString.
1348 setCounter(bv()->buffer(), par);
1350 string const & newLabel = par->params().labelString();
1352 if (oldLabel.empty() && !newLabel.empty()) {
1353 removeParagraph(row);
1354 appendParagraph(row);
1362 void LyXText::insertInset(Inset * inset)
1364 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1366 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1368 cursor.par()->insertInset(cursor.pos(), inset);
1369 // Just to rebreak and refresh correctly.
1370 // The character will not be inserted a second time
1371 insertChar(Paragraph::META_INSET);
1372 // If we enter a highly editable inset the cursor should be to before
1373 // the inset. This couldn't happen before as Undo was not handled inside
1374 // inset now after the Undo LyX tries to call inset->Edit(...) again
1375 // and cannot do this as the cursor is behind the inset and GetInset
1376 // does not return the inset!
1377 if (isHighlyEditableInset(inset)) {
1384 void LyXText::copyEnvironmentType()
1386 copylayouttype = cursor.par()->layout()->name();
1390 void LyXText::pasteEnvironmentType()
1392 // do nothing if there has been no previous copyEnvironmentType()
1393 if (!copylayouttype.empty())
1394 setLayout(copylayouttype);
1398 void LyXText::cutSelection(bool doclear, bool realcut)
1400 // Stuff what we got on the clipboard. Even if there is no selection.
1402 // There is a problem with having the stuffing here in that the
1403 // larger the selection the slower LyX will get. This can be
1404 // solved by running the line below only when the selection has
1405 // finished. The solution used currently just works, to make it
1406 // faster we need to be more clever and probably also have more
1407 // calls to stuffClipboard. (Lgb)
1408 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1410 // This doesn't make sense, if there is no selection
1411 if (!selection.set())
1414 // OK, we have a selection. This is always between selection.start
1415 // and selection.end
1417 // make sure that the depth behind the selection are restored, too
1418 Paragraph * endpar = selection.end.par()->next();
1419 Paragraph * undoendpar = endpar;
1421 if (endpar && endpar->getDepth()) {
1422 while (endpar && endpar->getDepth()) {
1423 endpar = endpar->next();
1424 undoendpar = endpar;
1426 } else if (endpar) {
1427 endpar = endpar->next(); // because of parindents etc.
1430 setUndo(bv(), Undo::DELETE,
1431 selection.start.par(), undoendpar);
1433 // there are two cases: cut only within one paragraph or
1434 // more than one paragraph
1435 if (selection.start.par() == selection.end.par()) {
1436 // only within one paragraph
1437 endpar = selection.end.par();
1438 int pos = selection.end.pos();
1439 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1440 selection.start.pos(), pos,
1441 bv()->buffer()->params.textclass,
1443 selection.end.pos(pos);
1445 endpar = selection.end.par();
1446 int pos = selection.end.pos();
1447 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1448 selection.start.pos(), pos,
1449 bv()->buffer()->params.textclass,
1452 selection.end.par(endpar);
1453 selection.end.pos(pos);
1454 cursor.pos(selection.end.pos());
1456 endpar = endpar->next();
1458 // sometimes necessary
1460 selection.start.par()->stripLeadingSpaces();
1462 redoParagraphs(selection.start, endpar);
1464 // cutSelection can invalidate the cursor so we need to set
1466 // we prefer the end for when tracking changes
1467 cursor = selection.end;
1469 // need a valid cursor. (Lgb)
1472 setCursor(cursor.par(), cursor.pos());
1473 selection.cursor = cursor;
1478 void LyXText::copySelection()
1480 // stuff the selection onto the X clipboard, from an explicit copy request
1481 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1483 // this doesnt make sense, if there is no selection
1484 if (!selection.set())
1487 // ok we have a selection. This is always between selection.start
1488 // and sel_end cursor
1490 // copy behind a space if there is one
1491 while (selection.start.par()->size() > selection.start.pos()
1492 && selection.start.par()->isLineSeparator(selection.start.pos())
1493 && (selection.start.par() != selection.end.par()
1494 || selection.start.pos() < selection.end.pos()))
1495 selection.start.pos(selection.start.pos() + 1);
1497 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1498 selection.start.pos(), selection.end.pos(),
1499 bv()->buffer()->params.textclass);
1503 void LyXText::pasteSelection()
1505 // this does not make sense, if there is nothing to paste
1506 if (!CutAndPaste::checkPastePossible())
1509 setUndo(bv(), Undo::INSERT,
1510 cursor.par(), cursor.par()->next());
1513 Paragraph * actpar = cursor.par();
1514 int pos = cursor.pos();
1516 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1517 bv()->buffer()->params.textclass);
1519 redoParagraphs(cursor, endpar);
1521 setCursor(cursor.par(), cursor.pos());
1524 selection.cursor = cursor;
1525 setCursor(actpar, pos);
1531 void LyXText::setSelectionRange(lyx::pos_type length)
1536 selection.cursor = cursor;
1543 // simple replacing. The font of the first selected character is used
1544 void LyXText::replaceSelectionWithString(string const & str)
1546 setCursorParUndo(bv());
1549 if (!selection.set()) { // create a dummy selection
1550 selection.end = cursor;
1551 selection.start = cursor;
1554 // Get font setting before we cut
1555 pos_type pos = selection.end.pos();
1556 LyXFont const font = selection.start.par()
1557 ->getFontSettings(bv()->buffer()->params,
1558 selection.start.pos());
1560 // Insert the new string
1561 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1562 selection.end.par()->insertChar(pos, (*cit), font);
1566 // Cut the selection
1567 cutSelection(true, false);
1573 // needed to insert the selection
1574 void LyXText::insertStringAsLines(string const & str)
1576 Paragraph * par = cursor.par();
1577 pos_type pos = cursor.pos();
1578 Paragraph * endpar = cursor.par()->next();
1580 setCursorParUndo(bv());
1582 // only to be sure, should not be neccessary
1585 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1587 redoParagraphs(cursor, endpar);
1588 setCursor(cursor.par(), cursor.pos());
1589 selection.cursor = cursor;
1590 setCursor(par, pos);
1595 // turns double-CR to single CR, others where converted into one
1596 // blank. Then InsertStringAsLines is called
1597 void LyXText::insertStringAsParagraphs(string const & str)
1599 string linestr(str);
1600 bool newline_inserted = false;
1601 for (string::size_type i = 0; i < linestr.length(); ++i) {
1602 if (linestr[i] == '\n') {
1603 if (newline_inserted) {
1604 // we know that \r will be ignored by
1605 // InsertStringA. Of course, it is a dirty
1606 // trick, but it works...
1607 linestr[i - 1] = '\r';
1611 newline_inserted = true;
1613 } else if (IsPrintable(linestr[i])) {
1614 newline_inserted = false;
1617 insertStringAsLines(linestr);
1621 void LyXText::checkParagraph(Paragraph * par,
1624 LyXCursor tmpcursor;
1628 Row * row = getRow(par, pos, y);
1630 // is there a break one row above
1631 if (row->previous() && row->previous()->par() == row->par()) {
1632 z = rowBreakPoint(*row->previous());
1633 if (z >= row->pos()) {
1634 // set the dimensions of the row above
1635 y -= row->previous()->height();
1638 breakAgain(row->previous());
1640 // set the cursor again. Otherwise
1641 // dangling pointers are possible
1642 setCursor(cursor.par(), cursor.pos(),
1643 false, cursor.boundary());
1644 selection.cursor = cursor;
1649 int const tmpheight = row->height();
1650 pos_type const tmplast = row->lastPos();
1653 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1654 postRowPaint(row, y);
1659 // check the special right address boxes
1660 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1667 redoDrawingOfParagraph(tmpcursor);
1670 // set the cursor again. Otherwise dangling pointers are possible
1671 // also set the selection
1673 if (selection.set()) {
1675 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1676 false, selection.cursor.boundary());
1677 selection.cursor = cursor;
1678 setCursorIntern(selection.start.par(),
1679 selection.start.pos(),
1680 false, selection.start.boundary());
1681 selection.start = cursor;
1682 setCursorIntern(selection.end.par(),
1683 selection.end.pos(),
1684 false, selection.end.boundary());
1685 selection.end = cursor;
1686 setCursorIntern(last_sel_cursor.par(),
1687 last_sel_cursor.pos(),
1688 false, last_sel_cursor.boundary());
1689 last_sel_cursor = cursor;
1692 setCursorIntern(cursor.par(), cursor.pos(),
1693 false, cursor.boundary());
1697 // returns false if inset wasn't found
1698 bool LyXText::updateInset(Inset * inset)
1700 // first check the current paragraph
1701 int pos = cursor.par()->getPositionOfInset(inset);
1703 checkParagraph(cursor.par(), pos);
1707 // check every paragraph
1709 Paragraph * par = ownerParagraph();
1711 pos = par->getPositionOfInset(inset);
1713 checkParagraph(par, pos);
1723 bool LyXText::setCursor(Paragraph * par,
1725 bool setfont, bool boundary)
1727 LyXCursor old_cursor = cursor;
1728 setCursorIntern(par, pos, setfont, boundary);
1729 return deleteEmptyParagraphMechanism(old_cursor);
1733 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1734 pos_type pos, bool boundary)
1740 cur.boundary(boundary);
1742 // get the cursor y position in text
1744 Row * row = getRow(par, pos, y);
1745 Row * old_row = row;
1747 // if we are before the first char of this row and are still in the
1748 // same paragraph and there is a previous row then put the cursor on
1749 // the end of the previous row
1750 cur.iy(y + row->baseline());
1752 if (row->previous() && pos &&
1753 row->previous()->par() == row->par() &&
1754 pos < par->size() &&
1755 par->getChar(pos) == Paragraph::META_INSET &&
1756 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1758 row = row->previous();
1763 // y is now the beginning of the cursor row
1764 y += row->baseline();
1765 // y is now the cursor baseline
1768 pos_type last = old_row->lastPrintablePos();
1770 // None of these should happen, but we're scaredy-cats
1771 if (pos > par->size()) {
1772 lyxerr << "dont like 1 please report" << endl;
1775 } else if (pos > last + 1) {
1776 lyxerr << "dont like 2 please report" << endl;
1777 // This shouldn't happen.
1780 } else if (pos < row->pos()) {
1781 lyxerr << "dont like 3 please report" << endl;
1786 // now get the cursors x position
1787 float x = getCursorX(row, pos, last, boundary);
1790 if (old_row != row) {
1791 x = getCursorX(old_row, pos, last, boundary);
1795 //if the cursor is in a visible row, anchor to it
1797 if (topy < y && y < topy + bv()->workHeight())
1802 float LyXText::getCursorX(Row * row,
1803 pos_type pos, pos_type last, bool boundary) const
1805 pos_type cursor_vpos = 0;
1807 float fill_separator;
1809 float fill_label_hfill;
1810 // This call HAS to be here because of the BidiTables!!!
1811 prepareToPrint(row, x, fill_separator, fill_hfill,
1814 if (last < row->pos())
1815 cursor_vpos = row->pos();
1816 else if (pos > last && !boundary)
1817 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1818 ? row->pos() : last + 1;
1819 else if (pos > row->pos() &&
1820 (pos > last || boundary))
1821 /// Place cursor after char at (logical) position pos - 1
1822 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1823 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1825 /// Place cursor before char at (logical) position pos
1826 cursor_vpos = (bidi_level(pos) % 2 == 0)
1827 ? log2vis(pos) : log2vis(pos) + 1;
1829 pos_type body_pos = row->par()->beginningOfBody();
1830 if ((body_pos > 0) &&
1831 ((body_pos-1 > last) ||
1832 !row->par()->isLineSeparator(body_pos - 1)))
1835 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1836 pos_type pos = vis2log(vpos);
1837 if (body_pos > 0 && pos == body_pos - 1) {
1838 x += fill_label_hfill +
1839 font_metrics::width(
1840 row->par()->layout()->labelsep,
1841 getLabelFont(bv()->buffer(),
1843 if (row->par()->isLineSeparator(body_pos - 1))
1845 row->par(), body_pos - 1);
1847 if (row->hfillExpansion(pos)) {
1848 x += singleWidth(row->par(), pos);
1849 if (pos >= body_pos)
1852 x += fill_label_hfill;
1853 } else if (row->par()->isSeparator(pos)) {
1854 x += singleWidth(row->par(), pos);
1855 if (pos >= body_pos)
1856 x += fill_separator;
1858 x += singleWidth(row->par(), pos);
1864 void LyXText::setCursorIntern(Paragraph * par,
1865 pos_type pos, bool setfont, bool boundary)
1867 InsetText * it = static_cast<InsetText *>(par->inInset());
1869 if (it != inset_owner) {
1870 lyxerr[Debug::INSETS] << "InsetText is " << it
1872 << "inset_owner is "
1873 << inset_owner << endl;
1874 #ifdef WITH_WARNINGS
1875 #warning I believe this code is wrong. (Lgb)
1876 #warning Jürgen, have a look at this. (Lgb)
1877 #warning Hmmm, I guess you are right but we
1878 #warning should verify when this is needed
1880 // Jürgen, would you like to have a look?
1881 // I guess we need to move the outer cursor
1882 // and open and lock the inset (bla bla bla)
1883 // stuff I don't know... so can you have a look?
1885 // I moved the lyxerr stuff in here so we can see if
1886 // this is actually really needed and where!
1888 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1893 setCursor(cursor, par, pos, boundary);
1899 void LyXText::setCurrentFont()
1901 pos_type pos = cursor.pos();
1902 if (cursor.boundary() && pos > 0)
1906 if (pos == cursor.par()->size())
1908 else // potentional bug... BUG (Lgb)
1909 if (cursor.par()->isSeparator(pos)) {
1910 if (pos > cursor.row()->pos() &&
1911 bidi_level(pos) % 2 ==
1912 bidi_level(pos - 1) % 2)
1914 else if (pos + 1 < cursor.par()->size())
1920 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1921 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1923 if (cursor.pos() == cursor.par()->size() &&
1924 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1925 !cursor.boundary()) {
1926 Language const * lang =
1927 cursor.par()->getParLanguage(bv()->buffer()->params);
1928 current_font.setLanguage(lang);
1929 current_font.setNumber(LyXFont::OFF);
1930 real_current_font.setLanguage(lang);
1931 real_current_font.setNumber(LyXFont::OFF);
1936 // returns the column near the specified x-coordinate of the row
1937 // x is set to the real beginning of this column
1939 LyXText::getColumnNearX(Row * row, int & x,
1940 bool & boundary) const
1943 float fill_separator;
1945 float fill_label_hfill;
1947 prepareToPrint(row, tmpx, fill_separator,
1948 fill_hfill, fill_label_hfill);
1950 pos_type vc = row->pos();
1951 pos_type last = row->lastPrintablePos();
1954 LyXLayout_ptr const & layout = row->par()->layout();
1956 bool left_side = false;
1958 pos_type body_pos = row->par()->beginningOfBody();
1959 float last_tmpx = tmpx;
1962 (body_pos - 1 > last ||
1963 !row->par()->isLineSeparator(body_pos - 1)))
1966 // check for empty row
1967 if (!row->par()->size()) {
1972 while (vc <= last && tmpx <= x) {
1975 if (body_pos > 0 && c == body_pos-1) {
1976 tmpx += fill_label_hfill +
1977 font_metrics::width(layout->labelsep,
1978 getLabelFont(bv()->buffer(), row->par()));
1979 if (row->par()->isLineSeparator(body_pos - 1))
1980 tmpx -= singleWidth(row->par(), body_pos-1);
1983 if (row->hfillExpansion(c)) {
1984 tmpx += singleWidth(row->par(), c);
1988 tmpx += fill_label_hfill;
1989 } else if (row->par()->isSeparator(c)) {
1990 tmpx += singleWidth(row->par(), c);
1992 tmpx+= fill_separator;
1994 tmpx += singleWidth(row->par(), c);
1999 if ((tmpx + last_tmpx) / 2 > x) {
2004 if (vc > last + 1) // This shouldn't happen.
2008 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
2009 // some speedup if rtl_support=false
2010 && (!row->next() || row->next()->par() != row->par());
2011 bool const rtl = (lastrow)
2012 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
2013 : false; // If lastrow is false, we don't need to compute
2014 // the value of rtl.
2017 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
2018 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
2020 else if (vc == row->pos()) {
2022 if (bidi_level(c) % 2 == 1)
2025 c = vis2log(vc - 1);
2026 bool const rtl = (bidi_level(c) % 2 == 1);
2027 if (left_side == rtl) {
2029 boundary = isBoundary(bv()->buffer(), row->par(), c);
2033 if (row->pos() <= last && c > last
2034 && row->par()->isNewline(last)) {
2035 if (bidi_level(last) % 2 == 0)
2036 tmpx -= singleWidth(row->par(), last);
2038 tmpx += singleWidth(row->par(), last);
2048 void LyXText::setCursorFromCoordinates(int x, int y)
2050 LyXCursor old_cursor = cursor;
2052 setCursorFromCoordinates(cursor, x, y);
2054 deleteEmptyParagraphMechanism(old_cursor);
2061 * return true if the cursor given is at the end of a row,
2062 * and the next row is filled by an inset that spans an entire
2065 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2068 Row const & next = *row.next();
2070 if (next.pos() != cur.pos() || next.par() != cur.par())
2072 if (!cur.par()->isInset(cur.pos()))
2074 Inset const * inset = cur.par()->getInset(cur.pos());
2075 if (inset->needFullRow() || inset->display())
2082 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2085 // Get the row first.
2087 Row * row = getRowNearY(y);
2089 pos_type const column = getColumnNearX(row, x, bound);
2090 cur.par(row->par());
2091 cur.pos(row->pos() + column);
2093 cur.y(y + row->baseline());
2096 if (beforeFullRowInset(*row, cur)) {
2097 pos_type last = row->lastPrintablePos();
2098 float x = getCursorX(row->next(), cur.pos(), last, bound);
2100 cur.iy(y + row->height() + row->next()->baseline());
2101 cur.irow(row->next());
2107 cur.boundary(bound);
2111 void LyXText::cursorLeft(bool internal)
2113 if (cursor.pos() > 0) {
2114 bool boundary = cursor.boundary();
2115 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2116 if (!internal && !boundary &&
2117 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2118 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2119 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2120 Paragraph * par = cursor.par()->previous();
2121 setCursor(par, par->size());
2126 void LyXText::cursorRight(bool internal)
2128 if (!internal && cursor.boundary() &&
2129 !cursor.par()->isNewline(cursor.pos()))
2130 setCursor(cursor.par(), cursor.pos(), true, false);
2131 else if (cursor.pos() < cursor.par()->size()) {
2132 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2134 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2135 setCursor(cursor.par(), cursor.pos(), true, true);
2136 } else if (cursor.par()->next())
2137 setCursor(cursor.par()->next(), 0);
2141 void LyXText::cursorUp(bool selecting)
2144 int x = cursor.x_fix();
2145 int y = cursor.y() - cursor.row()->baseline() - 1;
2146 setCursorFromCoordinates(x, y);
2149 int y1 = cursor.iy() - topy;
2152 Inset * inset_hit = checkInsetHit(x, y1);
2153 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2154 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2158 setCursorFromCoordinates(bv(), cursor.x_fix(),
2159 cursor.y() - cursor.row()->baseline() - 1);
2164 void LyXText::cursorDown(bool selecting)
2167 int x = cursor.x_fix();
2168 int y = cursor.y() - cursor.row()->baseline() +
2169 cursor.row()->height() + 1;
2170 setCursorFromCoordinates(x, y);
2171 if (!selecting && cursor.row() == cursor.irow()) {
2173 int y1 = cursor.iy() - topy;
2176 Inset * inset_hit = checkInsetHit(x, y1);
2177 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2178 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2182 setCursorFromCoordinates(bv(), cursor.x_fix(),
2183 cursor.y() - cursor.row()->baseline()
2184 + cursor.row()->height() + 1);
2189 void LyXText::cursorUpParagraph()
2191 if (cursor.pos() > 0) {
2192 setCursor(cursor.par(), 0);
2194 else if (cursor.par()->previous()) {
2195 setCursor(cursor.par()->previous(), 0);
2200 void LyXText::cursorDownParagraph()
2202 if (cursor.par()->next()) {
2203 setCursor(cursor.par()->next(), 0);
2205 setCursor(cursor.par(), cursor.par()->size());
2209 // fix the cursor `cur' after a characters has been deleted at `where'
2210 // position. Called by deleteEmptyParagraphMechanism
2211 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2212 LyXCursor const & where)
2214 // if cursor is not in the paragraph where the delete occured,
2216 if (cur.par() != where.par())
2219 // if cursor position is after the place where the delete occured,
2221 if (cur.pos() > where.pos())
2222 cur.pos(cur.pos()-1);
2224 // check also if we don't want to set the cursor on a spot behind the
2225 // pagragraph because we erased the last character.
2226 if (cur.pos() > cur.par()->size())
2227 cur.pos(cur.par()->size());
2229 // recompute row et al. for this cursor
2230 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2234 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2236 // Would be wrong to delete anything if we have a selection.
2237 if (selection.set())
2240 // We allow all kinds of "mumbo-jumbo" when freespacing.
2241 if (old_cursor.par()->layout()->free_spacing
2242 || old_cursor.par()->isFreeSpacing()) {
2246 /* Ok I'll put some comments here about what is missing.
2247 I have fixed BackSpace (and thus Delete) to not delete
2248 double-spaces automagically. I have also changed Cut,
2249 Copy and Paste to hopefully do some sensible things.
2250 There are still some small problems that can lead to
2251 double spaces stored in the document file or space at
2252 the beginning of paragraphs. This happens if you have
2253 the cursor betwenn to spaces and then save. Or if you
2254 cut and paste and the selection have a space at the
2255 beginning and then save right after the paste. I am
2256 sure none of these are very hard to fix, but I will
2257 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2258 that I can get some feedback. (Lgb)
2261 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2262 // delete the LineSeparator.
2265 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2266 // delete the LineSeparator.
2269 // If the pos around the old_cursor were spaces, delete one of them.
2270 if (old_cursor.par() != cursor.par()
2271 || old_cursor.pos() != cursor.pos()) {
2272 // Only if the cursor has really moved
2274 if (old_cursor.pos() > 0
2275 && old_cursor.pos() < old_cursor.par()->size()
2276 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2277 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2278 old_cursor.par()->erase(old_cursor.pos() - 1);
2279 redoParagraphs(old_cursor, old_cursor.par()->next());
2281 #ifdef WITH_WARNINGS
2282 #warning This will not work anymore when we have multiple views of the same buffer
2283 // In this case, we will have to correct also the cursors held by
2284 // other bufferviews. It will probably be easier to do that in a more
2285 // automated way in LyXCursor code. (JMarc 26/09/2001)
2287 // correct all cursors held by the LyXText
2288 fixCursorAfterDelete(cursor, old_cursor);
2289 fixCursorAfterDelete(selection.cursor,
2291 fixCursorAfterDelete(selection.start,
2293 fixCursorAfterDelete(selection.end, old_cursor);
2294 fixCursorAfterDelete(last_sel_cursor,
2296 fixCursorAfterDelete(toggle_cursor, old_cursor);
2297 fixCursorAfterDelete(toggle_end_cursor,
2303 // don't delete anything if this is the ONLY paragraph!
2304 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2307 // Do not delete empty paragraphs with keepempty set.
2308 if (old_cursor.par()->layout()->keepempty)
2311 // only do our magic if we changed paragraph
2312 if (old_cursor.par() == cursor.par())
2315 // record if we have deleted a paragraph
2316 // we can't possibly have deleted a paragraph before this point
2317 bool deleted = false;
2319 if ((old_cursor.par()->empty()
2320 || (old_cursor.par()->size() == 1
2321 && old_cursor.par()->isLineSeparator(0)))) {
2322 // ok, we will delete anything
2323 LyXCursor tmpcursor;
2327 if (old_cursor.row()->previous()) {
2328 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2329 - old_cursor.row()->previous()->height());
2331 cursor = old_cursor; // that undo can restore the right cursor position
2332 Paragraph * endpar = old_cursor.par()->next();
2333 if (endpar && endpar->getDepth()) {
2334 while (endpar && endpar->getDepth()) {
2335 endpar = endpar->next();
2338 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2342 removeRow(old_cursor.row());
2343 if (ownerParagraph() == old_cursor.par()) {
2344 ownerParagraph(ownerParagraph()->next());
2347 delete old_cursor.par();
2349 /* Breakagain the next par. Needed because of
2350 * the parindent that can occur or dissappear.
2351 * The next row can change its height, if
2352 * there is another layout before */
2353 if (refresh_row->next()) {
2354 breakAgain(refresh_row->next());
2357 setHeightOfRow(refresh_row);
2359 Row * nextrow = old_cursor.row()->next();
2360 const_cast<LyXText *>(this)->postPaint(
2361 old_cursor.y() - old_cursor.row()->baseline());
2364 cursor = old_cursor; // that undo can restore the right cursor position
2365 Paragraph * endpar = old_cursor.par()->next();
2366 if (endpar && endpar->getDepth()) {
2367 while (endpar && endpar->getDepth()) {
2368 endpar = endpar->next();
2371 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2375 removeRow(old_cursor.row());
2377 if (ownerParagraph() == old_cursor.par()) {
2378 ownerParagraph(ownerParagraph()->next());
2381 delete old_cursor.par();
2383 /* Breakagain the next par. Needed because of
2384 the parindent that can occur or dissappear.
2385 The next row can change its height, if
2386 there is another layout before */
2388 breakAgain(nextrow);
2394 setCursorIntern(cursor.par(), cursor.pos());
2396 if (selection.cursor.par() == old_cursor.par()
2397 && selection.cursor.pos() == old_cursor.pos()) {
2398 // correct selection
2399 selection.cursor = cursor;
2403 if (old_cursor.par()->stripLeadingSpaces()) {
2404 redoParagraphs(old_cursor,
2405 old_cursor.par()->next());
2407 setCursorIntern(cursor.par(), cursor.pos());
2408 selection.cursor = cursor;
2415 Paragraph * LyXText::ownerParagraph() const
2418 return inset_owner->paragraph();
2420 return &*(bv_owner->buffer()->paragraphs.begin());
2424 void LyXText::ownerParagraph(Paragraph * p) const
2427 inset_owner->paragraph(p);
2429 bv_owner->buffer()->paragraphs.set(p);
2434 void LyXText::ownerParagraph(int id, Paragraph * p) const
2436 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2437 if (op && op->inInset()) {
2438 static_cast<InsetText *>(op->inInset())->paragraph(p);
2445 LyXText::text_status LyXText::status() const
2451 void LyXText::clearPaint()
2453 status_ = UNCHANGED;
2459 void LyXText::postChangedInDraw()
2461 status_ = CHANGED_IN_DRAW;
2465 void LyXText::postPaint(int start_y)
2467 text_status old = status_;
2469 status_ = NEED_MORE_REFRESH;
2472 if (old != UNCHANGED && refresh_y < start_y) {
2476 refresh_y = start_y;
2481 // We are an inset's lyxtext. Tell the top-level lyxtext
2482 // it needs to update the row we're in.
2483 LyXText * t = bv()->text;
2484 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2488 // FIXME: we should probably remove this y parameter,
2489 // make refresh_y be 0, and use row->y etc.
2490 void LyXText::postRowPaint(Row * row, int start_y)
2492 if (status_ != UNCHANGED && refresh_y < start_y) {
2493 status_ = NEED_MORE_REFRESH;
2496 refresh_y = start_y;
2499 if (status_ == NEED_MORE_REFRESH)
2502 status_ = NEED_VERY_LITTLE_REFRESH;
2508 // We are an inset's lyxtext. Tell the top-level lyxtext
2509 // it needs to update the row we're in.
2510 LyXText * t = bv()->text;
2511 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2515 bool LyXText::isInInset() const
2517 // Sub-level has non-null bv owner and
2518 // non-null inset owner.
2519 return inset_owner != 0 && bv_owner != 0;
2523 int defaultRowHeight()
2525 LyXFont const font(LyXFont::ALL_SANE);
2526 return int(font_metrics::maxAscent(font)
2527 + font_metrics::maxDescent(font) * 1.5);