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_ += row_prev->height();
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
374 appendParagraph(firstrow);
376 appendParagraph(row->next());
381 Inset * LyXText::getInset() const
383 if (cursor.pos() < cursor.par()->size()
384 && cursor.par()->isInset(cursor.pos())) {
385 return cursor.par()->getInset(cursor.pos());
391 void LyXText::toggleInset()
393 Inset * inset = getInset();
394 // is there an editable inset at cursor position?
395 if (!isEditableInset(inset)) {
396 // No, try to see if we are inside a collapsable inset
397 if (inset_owner && inset_owner->owner()
398 && inset_owner->owner()->isOpen()) {
399 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
400 inset_owner->owner()->close(bv());
401 bv()->getLyXText()->cursorRight(bv());
405 //bv()->owner()->message(inset->editMessage());
407 // do we want to keep this?? (JMarc)
408 if (!isHighlyEditableInset(inset))
409 setCursorParUndo(bv());
411 if (inset->isOpen()) {
417 bv()->updateInset(inset);
421 /* used in setlayout */
422 // Asger is not sure we want to do this...
423 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
426 LyXLayout_ptr const & layout = par.layout();
429 for (pos_type pos = 0; pos < par.size(); ++pos) {
430 if (pos < par.beginningOfBody())
431 layoutfont = layout->labelfont;
433 layoutfont = layout->font;
435 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
436 tmpfont.reduce(layoutfont);
437 par.setFont(pos, tmpfont);
442 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
443 LyXCursor & send_cur,
444 string const & layout)
446 Paragraph * endpar = send_cur.par()->next();
447 Paragraph * undoendpar = endpar;
449 if (endpar && endpar->getDepth()) {
450 while (endpar && endpar->getDepth()) {
451 endpar = endpar->next();
455 endpar = endpar->next(); // because of parindents etc.
458 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
460 // ok we have a selection. This is always between sstart_cur
461 // and sel_end cursor
463 Paragraph * par = sstart_cur.par();
464 Paragraph * epar = send_cur.par()->next();
466 LyXLayout_ptr const & lyxlayout =
467 bv()->buffer()->params.getLyXTextClass()[layout];
470 par->applyLayout(lyxlayout);
471 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
472 Paragraph * fppar = par;
473 fppar->params().spaceTop(lyxlayout->fill_top ?
474 VSpace(VSpace::VFILL)
475 : VSpace(VSpace::NONE));
476 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
477 VSpace(VSpace::VFILL)
478 : VSpace(VSpace::NONE));
479 if (lyxlayout->margintype == MARGIN_MANUAL)
480 par->setLabelWidthString(lyxlayout->labelstring());
483 } while (par != epar);
489 // set layout over selection and make a total rebreak of those paragraphs
490 void LyXText::setLayout(string const & layout)
492 LyXCursor tmpcursor = cursor; /* store the current cursor */
494 // if there is no selection just set the layout
495 // of the current paragraph */
496 if (!selection.set()) {
497 selection.start = cursor; // dummy selection
498 selection.end = cursor;
500 Paragraph * endpar = setLayout(cursor, selection.start,
501 selection.end, layout);
502 redoParagraphs(selection.start, endpar);
504 // we have to reset the selection, because the
505 // geometry could have changed
506 setCursor(selection.start.par(),
507 selection.start.pos(), false);
508 selection.cursor = cursor;
509 setCursor(selection.end.par(), selection.end.pos(), false);
513 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
517 // increment depth over selection and
518 // make a total rebreak of those paragraphs
519 void LyXText::incDepth()
521 // If there is no selection, just use the current paragraph
522 if (!selection.set()) {
523 selection.start = cursor; // dummy selection
524 selection.end = cursor;
527 // We end at the next paragraph with depth 0
528 Paragraph * endpar = selection.end.par()->next();
530 Paragraph * undoendpar = endpar;
532 if (endpar && endpar->getDepth()) {
533 while (endpar && endpar->getDepth()) {
534 endpar = endpar->next();
538 endpar = endpar->next(); // because of parindents etc.
541 setUndo(bv(), Undo::EDIT,
542 selection.start.par(), undoendpar);
544 LyXCursor tmpcursor = cursor; // store the current cursor
546 // ok we have a selection. This is always between sel_start_cursor
547 // and sel_end cursor
548 cursor = selection.start;
551 // NOTE: you can't change the depth of a bibliography entry
552 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
553 Paragraph * prev = cursor.par()->previous();
556 if (cursor.par()->getDepth()
557 < prev->getMaxDepthAfter()) {
558 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
562 if (cursor.par() == selection.end.par())
564 cursor.par(cursor.par()->next());
567 redoParagraphs(selection.start, endpar);
569 // we have to reset the selection, because the
570 // geometry could have changed
571 setCursor(selection.start.par(), selection.start.pos());
572 selection.cursor = cursor;
573 setCursor(selection.end.par(), selection.end.pos());
577 setCursor(tmpcursor.par(), tmpcursor.pos());
581 // decrement depth over selection and
582 // make a total rebreak of those paragraphs
583 void LyXText::decDepth()
585 // if there is no selection just set the layout
586 // of the current paragraph
587 if (!selection.set()) {
588 selection.start = cursor; // dummy selection
589 selection.end = cursor;
591 Paragraph * endpar = selection.end.par()->next();
592 Paragraph * undoendpar = endpar;
594 if (endpar && endpar->getDepth()) {
595 while (endpar && endpar->getDepth()) {
596 endpar = endpar->next();
600 endpar = endpar->next(); // because of parindents etc.
603 setUndo(bv(), Undo::EDIT,
604 selection.start.par(), undoendpar);
606 LyXCursor tmpcursor = cursor; // store the current cursor
608 // ok we have a selection. This is always between sel_start_cursor
609 // and sel_end cursor
610 cursor = selection.start;
613 if (cursor.par()->params().depth()) {
614 cursor.par()->params()
615 .depth(cursor.par()->params().depth() - 1);
617 if (cursor.par() == selection.end.par()) {
620 cursor.par(cursor.par()->next());
623 redoParagraphs(selection.start, endpar);
625 // we have to reset the selection, because the
626 // geometry could have changed
627 setCursor(selection.start.par(),
628 selection.start.pos());
629 selection.cursor = cursor;
630 setCursor(selection.end.par(), selection.end.pos());
634 setCursor(tmpcursor.par(), tmpcursor.pos());
638 // set font over selection and make a total rebreak of those paragraphs
639 void LyXText::setFont(LyXFont const & font, bool toggleall)
641 // if there is no selection just set the current_font
642 if (!selection.set()) {
643 // Determine basis font
645 if (cursor.pos() < cursor.par()->beginningOfBody()) {
646 layoutfont = getLabelFont(bv()->buffer(),
649 layoutfont = getLayoutFont(bv()->buffer(),
652 // Update current font
653 real_current_font.update(font,
654 bv()->buffer()->params.language,
657 // Reduce to implicit settings
658 current_font = real_current_font;
659 current_font.reduce(layoutfont);
660 // And resolve it completely
661 real_current_font.realize(layoutfont);
666 LyXCursor tmpcursor = cursor; // store the current cursor
668 // ok we have a selection. This is always between sel_start_cursor
669 // and sel_end cursor
671 setUndo(bv(), Undo::EDIT,
672 selection.start.par(), selection.end.par()->next());
674 cursor = selection.start;
675 while (cursor.par() != selection.end.par() ||
676 cursor.pos() < selection.end.pos())
678 if (cursor.pos() < cursor.par()->size()) {
679 // an open footnote should behave like a closed one
680 setCharFont(cursor.par(), cursor.pos(),
682 cursor.pos(cursor.pos() + 1);
685 cursor.par(cursor.par()->next());
690 redoParagraphs(selection.start, selection.end.par()->next());
692 // we have to reset the selection, because the
693 // geometry could have changed, but we keep
694 // it for user convenience
695 setCursor(selection.start.par(), selection.start.pos());
696 selection.cursor = cursor;
697 setCursor(selection.end.par(), selection.end.pos());
699 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
700 tmpcursor.boundary());
704 void LyXText::redoHeightOfParagraph()
706 Row * tmprow = cursor.row();
707 int y = cursor.y() - tmprow->baseline();
709 setHeightOfRow(tmprow);
711 while (tmprow->previous()
712 && tmprow->previous()->par() == tmprow->par()) {
713 tmprow = tmprow->previous();
714 y -= tmprow->height();
715 setHeightOfRow(tmprow);
720 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
724 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
726 Row * tmprow = cur.row();
728 int y = cur.y() - tmprow->baseline();
729 setHeightOfRow(tmprow);
731 while (tmprow->previous()
732 && tmprow->previous()->par() == tmprow->par()) {
733 tmprow = tmprow->previous();
734 y -= tmprow->height();
738 setCursor(cur.par(), cur.pos());
742 // deletes and inserts again all paragaphs between the cursor
743 // and the specified par
744 // This function is needed after SetLayout and SetFont etc.
745 void LyXText::redoParagraphs(LyXCursor const & cur,
746 Paragraph const * endpar)
749 Paragraph * tmppar = 0;
750 Paragraph * first_phys_par = 0;
752 Row * tmprow = cur.row();
754 int y = cur.y() - tmprow->baseline();
756 if (!tmprow->previous()) {
757 // a trick/hack for UNDO
758 // This is needed because in an UNDO/REDO we could have changed
759 // the ownerParagrah() so the paragraph inside the row is NOT
760 // my really first par anymore. Got it Lars ;) (Jug 20011206)
761 first_phys_par = ownerParagraph();
763 first_phys_par = tmprow->par();
764 while (tmprow->previous()
765 && tmprow->previous()->par() == first_phys_par)
767 tmprow = tmprow->previous();
768 y -= tmprow->height();
772 Row * prevrow = tmprow->previous();
776 tmppar = tmprow->next()->par();
779 while (tmprow->next() && tmppar != endpar) {
780 removeRow(tmprow->next());
781 if (tmprow->next()) {
782 tmppar = tmprow->next()->par();
788 // remove the first one
789 tmprow2 = tmprow; /* this is because tmprow->previous()
791 tmprow = tmprow->previous();
794 tmppar = first_phys_par;
798 insertParagraph(tmppar, tmprow);
802 while (tmprow->next()
803 && tmprow->next()->par() == tmppar) {
804 tmprow = tmprow->next();
806 tmppar = tmppar->next();
808 } while (tmppar && tmppar != endpar);
810 // this is because of layout changes
812 setHeightOfRow(prevrow);
813 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
815 setHeightOfRow(firstrow);
816 const_cast<LyXText *>(this)->postPaint(0);
819 if (tmprow && tmprow->next())
820 setHeightOfRow(tmprow->next());
825 void LyXText::fullRebreak()
831 if (need_break_row) {
832 breakAgain(need_break_row);
839 // important for the screen
842 // the cursor set functions have a special mechanism. When they
843 // realize, that you left an empty paragraph, they will delete it.
844 // They also delete the corresponding row
846 // need the selection cursor:
847 void LyXText::setSelection()
849 bool const lsel = selection.set();
851 if (!selection.set()) {
852 last_sel_cursor = selection.cursor;
853 selection.start = selection.cursor;
854 selection.end = selection.cursor;
859 // first the toggling area
860 if (cursor.y() < last_sel_cursor.y()
861 || (cursor.y() == last_sel_cursor.y()
862 && cursor.x() < last_sel_cursor.x())) {
863 toggle_end_cursor = last_sel_cursor;
864 toggle_cursor = cursor;
866 toggle_end_cursor = cursor;
867 toggle_cursor = last_sel_cursor;
870 last_sel_cursor = cursor;
872 // and now the whole selection
874 if (selection.cursor.par() == cursor.par())
875 if (selection.cursor.pos() < cursor.pos()) {
876 selection.end = cursor;
877 selection.start = selection.cursor;
879 selection.end = selection.cursor;
880 selection.start = cursor;
882 else if (selection.cursor.y() < cursor.y() ||
883 (selection.cursor.y() == cursor.y()
884 && selection.cursor.x() < cursor.x())) {
885 selection.end = cursor;
886 selection.start = selection.cursor;
889 selection.end = selection.cursor;
890 selection.start = cursor;
893 // a selection with no contents is not a selection
894 if (selection.start.par() == selection.end.par() &&
895 selection.start.pos() == selection.end.pos())
896 selection.set(false);
898 if (inset_owner && (selection.set() || lsel))
899 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
903 string const LyXText::selectionAsString(Buffer const * buffer,
906 if (!selection.set()) return string();
908 // should be const ...
909 Paragraph * startpar(selection.start.par());
910 Paragraph * endpar(selection.end.par());
911 pos_type const startpos(selection.start.pos());
912 pos_type const endpos(selection.end.pos());
914 if (startpar == endpar) {
915 return startpar->asString(buffer, startpos, endpos, label);
920 // First paragraph in selection
921 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
923 // The paragraphs in between (if any)
924 LyXCursor tmpcur(selection.start);
925 tmpcur.par(tmpcur.par()->next());
926 while (tmpcur.par() != endpar) {
927 result += tmpcur.par()->asString(buffer, 0,
928 tmpcur.par()->size(),
930 tmpcur.par(tmpcur.par()->next());
933 // Last paragraph in selection
934 result += endpar->asString(buffer, 0, endpos, label);
940 void LyXText::clearSelection()
942 selection.set(false);
943 selection.mark(false);
944 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
945 // reset this in the bv_owner!
946 if (bv_owner && bv_owner->text)
947 bv_owner->text->xsel_cache.set(false);
951 void LyXText::cursorHome()
953 setCursor(cursor.par(), cursor.row()->pos());
957 void LyXText::cursorEnd()
959 if (!cursor.row()->next()
960 || cursor.row()->next()->par() != cursor.row()->par()) {
961 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
963 if (!cursor.par()->empty() &&
964 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
965 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
966 setCursor(cursor.par(), cursor.row()->lastPos());
968 setCursor(cursor.par(),
969 cursor.row()->lastPos() + 1);
975 void LyXText::cursorTop()
977 while (cursor.par()->previous())
978 cursor.par(cursor.par()->previous());
979 setCursor(cursor.par(), 0);
983 void LyXText::cursorBottom()
985 while (cursor.par()->next())
986 cursor.par(cursor.par()->next());
987 setCursor(cursor.par(), cursor.par()->size());
991 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
993 // If the mask is completely neutral, tell user
994 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
995 // Could only happen with user style
996 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
1000 // Try implicit word selection
1001 // If there is a change in the language the implicit word selection
1003 LyXCursor resetCursor = cursor;
1004 bool implicitSelection = (font.language() == ignore_language
1005 && font.number() == LyXFont::IGNORE)
1006 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
1009 setFont(font, toggleall);
1011 // Implicit selections are cleared afterwards
1012 //and cursor is set to the original position.
1013 if (implicitSelection) {
1015 cursor = resetCursor;
1016 setCursor(cursor.par(), cursor.pos());
1017 selection.cursor = cursor;
1020 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
1024 string LyXText::getStringToIndex()
1026 // Try implicit word selection
1027 // If there is a change in the language the implicit word selection
1029 LyXCursor const reset_cursor = cursor;
1030 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
1033 if (!selection.set())
1034 bv()->owner()->message(_("Nothing to index!"));
1035 else if (selection.start.par() != selection.end.par())
1036 bv()->owner()->message(_("Cannot index more than one paragraph!"));
1038 idxstring = selectionAsString(bv()->buffer(), false);
1040 // Reset cursors to their original position.
1041 cursor = reset_cursor;
1042 setCursor(cursor.par(), cursor.pos());
1043 selection.cursor = cursor;
1045 // Clear the implicit selection.
1046 if (implicitSelection)
1053 // the DTP switches for paragraphs. LyX will store them in the first
1054 // physicla paragraph. When a paragraph is broken, the top settings rest,
1055 // the bottom settings are given to the new one. So I can make shure,
1056 // they do not duplicate themself and you cannnot make dirty things with
1059 void LyXText::setParagraph(bool line_top, bool line_bottom,
1060 bool pagebreak_top, bool pagebreak_bottom,
1061 VSpace const & space_top,
1062 VSpace const & space_bottom,
1063 Spacing const & spacing,
1065 string const & labelwidthstring,
1068 LyXCursor tmpcursor = cursor;
1069 if (!selection.set()) {
1070 selection.start = cursor;
1071 selection.end = cursor;
1074 // make sure that the depth behind the selection are restored, too
1075 Paragraph * endpar = selection.end.par()->next();
1076 Paragraph * undoendpar = endpar;
1078 if (endpar && endpar->getDepth()) {
1079 while (endpar && endpar->getDepth()) {
1080 endpar = endpar->next();
1081 undoendpar = endpar;
1085 // because of parindents etc.
1086 endpar = endpar->next();
1089 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1092 Paragraph * tmppar = selection.end.par();
1094 while (tmppar != selection.start.par()->previous()) {
1095 setCursor(tmppar, 0);
1096 postPaint(cursor.y() - cursor.row()->baseline());
1097 cursor.par()->params().lineTop(line_top);
1098 cursor.par()->params().lineBottom(line_bottom);
1099 cursor.par()->params().pagebreakTop(pagebreak_top);
1100 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1101 cursor.par()->params().spaceTop(space_top);
1102 cursor.par()->params().spaceBottom(space_bottom);
1103 cursor.par()->params().spacing(spacing);
1104 // does the layout allow the new alignment?
1105 LyXLayout_ptr const & layout = cursor.par()->layout();
1107 if (align == LYX_ALIGN_LAYOUT)
1108 align = layout->align;
1109 if (align & layout->alignpossible) {
1110 if (align == layout->align)
1111 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1113 cursor.par()->params().align(align);
1115 cursor.par()->setLabelWidthString(labelwidthstring);
1116 cursor.par()->params().noindent(noindent);
1117 tmppar = cursor.par()->previous();
1120 redoParagraphs(selection.start, endpar);
1123 setCursor(selection.start.par(), selection.start.pos());
1124 selection.cursor = cursor;
1125 setCursor(selection.end.par(), selection.end.pos());
1127 setCursor(tmpcursor.par(), tmpcursor.pos());
1129 bv()->updateInset(inset_owner);
1133 // set the counter of a paragraph. This includes the labels
1134 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1136 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1137 LyXLayout_ptr const & layout = par->layout();
1139 if (par->previous()) {
1141 par->params().appendix(par->previous()->params().appendix());
1142 if (!par->params().appendix() && par->params().startOfAppendix()) {
1143 par->params().appendix(true);
1144 textclass.counters().reset();
1146 par->enumdepth = par->previous()->enumdepth;
1147 par->itemdepth = par->previous()->itemdepth;
1149 par->params().appendix(par->params().startOfAppendix());
1154 /* Maybe we have to increment the enumeration depth.
1155 * BUT, enumeration in a footnote is considered in isolation from its
1156 * surrounding paragraph so don't increment if this is the
1157 * first line of the footnote
1158 * AND, bibliographies can't have their depth changed ie. they
1159 * are always of depth 0
1162 && par->previous()->getDepth() < par->getDepth()
1163 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1164 && par->enumdepth < 3
1165 && layout->labeltype != LABEL_BIBLIO) {
1169 // Maybe we have to decrement the enumeration depth, see note above
1171 && par->previous()->getDepth() > par->getDepth()
1172 && layout->labeltype != LABEL_BIBLIO) {
1173 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1176 if (!par->params().labelString().empty()) {
1177 par->params().labelString(string());
1180 if (layout->margintype == MARGIN_MANUAL) {
1181 if (par->params().labelWidthString().empty()) {
1182 par->setLabelWidthString(layout->labelstring());
1185 par->setLabelWidthString(string());
1188 // is it a layout that has an automatic label?
1189 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1190 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1194 if (i >= 0 && i <= buf->params.secnumdepth) {
1198 textclass.counters().step(layout->latexname());
1200 // Is there a label? Useful for Chapter layout
1201 if (!par->params().appendix()) {
1202 s << layout->labelstring();
1204 s << layout->labelstring_appendix();
1207 // Use of an integer is here less than elegant. For now.
1208 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1209 if (!par->params().appendix()) {
1210 numbertype = "sectioning";
1212 numbertype = "appendix";
1213 if (par->isRightToLeftPar(buf->params))
1214 langtype = "hebrew";
1219 s << textclass.counters()
1220 .numberLabel(layout->latexname(),
1221 numbertype, langtype, head);
1223 par->params().labelString(STRCONV(s.str()));
1225 // reset enum counters
1226 textclass.counters().reset("enum");
1227 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1228 textclass.counters().reset("enum");
1229 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1231 // Yes I know this is a really, really! bad solution
1233 string enumcounter("enum");
1235 switch (par->enumdepth) {
1244 enumcounter += "iv";
1247 // not a valid enumdepth...
1251 textclass.counters().step(enumcounter);
1253 s << textclass.counters()
1254 .numberLabel(enumcounter, "enumeration");
1255 par->params().labelString(STRCONV(s.str()));
1257 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1258 textclass.counters().step("bibitem");
1259 int number = textclass.counters().value("bibitem");
1260 if (par->bibitem()) {
1261 par->bibitem()->setCounter(number);
1262 par->params().labelString(layout->labelstring());
1264 // In biblio should't be following counters but...
1266 string s = layout->labelstring();
1268 // the caption hack:
1269 if (layout->labeltype == LABEL_SENSITIVE) {
1270 Paragraph * tmppar = par;
1273 while (tmppar && tmppar->inInset()
1274 // the single '=' is intended below
1275 && (in = tmppar->inInset()->owner())) {
1276 if (in->lyxCode() == Inset::FLOAT_CODE ||
1277 in->lyxCode() == Inset::WRAP_CODE) {
1281 tmppar = in->parOwner();
1287 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1289 textclass.counters().step(fl.type());
1291 // Doesn't work... yet.
1292 #warning use boost.format
1293 #if USE_BOOST_FORMAT
1294 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1295 // s << boost::format(_("%1$s %1$d:")
1297 // % buf->counters().value(fl.name());
1300 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1301 o << fl.name() << " #:";
1302 s = STRCONV(o.str());
1305 // par->SetLayout(0);
1306 // s = layout->labelstring;
1307 s = _("Senseless: ");
1310 par->params().labelString(s);
1312 // reset the enumeration counter. They are always reset
1313 // when there is any other layout between
1314 // Just fall-through between the cases so that all
1315 // enum counters deeper than enumdepth is also reset.
1316 switch (par->enumdepth) {
1318 textclass.counters().reset("enumi");
1320 textclass.counters().reset("enumii");
1322 textclass.counters().reset("enumiii");
1324 textclass.counters().reset("enumiv");
1330 // Updates all counters. Paragraphs with changed label string will be rebroken
1331 void LyXText::updateCounters()
1333 Row * row = firstrow;
1334 Paragraph * par = row->par();
1336 // CHECK if this is really needed. (Lgb)
1337 bv()->buffer()->params.getLyXTextClass().counters().reset();
1340 while (row->par() != par)
1343 string const oldLabel = par->params().labelString();
1345 // setCounter can potentially change the labelString.
1346 setCounter(bv()->buffer(), par);
1348 string const & newLabel = par->params().labelString();
1350 if (oldLabel.empty() && !newLabel.empty()) {
1351 removeParagraph(row);
1352 appendParagraph(row);
1360 void LyXText::insertInset(Inset * inset)
1362 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1364 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1366 cursor.par()->insertInset(cursor.pos(), inset);
1367 // Just to rebreak and refresh correctly.
1368 // The character will not be inserted a second time
1369 insertChar(Paragraph::META_INSET);
1370 // If we enter a highly editable inset the cursor should be to before
1371 // the inset. This couldn't happen before as Undo was not handled inside
1372 // inset now after the Undo LyX tries to call inset->Edit(...) again
1373 // and cannot do this as the cursor is behind the inset and GetInset
1374 // does not return the inset!
1375 if (isHighlyEditableInset(inset)) {
1382 void LyXText::copyEnvironmentType()
1384 copylayouttype = cursor.par()->layout()->name();
1388 void LyXText::pasteEnvironmentType()
1390 // do nothing if there has been no previous copyEnvironmentType()
1391 if (!copylayouttype.empty())
1392 setLayout(copylayouttype);
1396 void LyXText::cutSelection(bool doclear, bool realcut)
1398 // Stuff what we got on the clipboard. Even if there is no selection.
1400 // There is a problem with having the stuffing here in that the
1401 // larger the selection the slower LyX will get. This can be
1402 // solved by running the line below only when the selection has
1403 // finished. The solution used currently just works, to make it
1404 // faster we need to be more clever and probably also have more
1405 // calls to stuffClipboard. (Lgb)
1406 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1408 // This doesn't make sense, if there is no selection
1409 if (!selection.set())
1412 // OK, we have a selection. This is always between selection.start
1413 // and selection.end
1415 // make sure that the depth behind the selection are restored, too
1416 Paragraph * endpar = selection.end.par()->next();
1417 Paragraph * undoendpar = endpar;
1419 if (endpar && endpar->getDepth()) {
1420 while (endpar && endpar->getDepth()) {
1421 endpar = endpar->next();
1422 undoendpar = endpar;
1424 } else if (endpar) {
1425 endpar = endpar->next(); // because of parindents etc.
1428 setUndo(bv(), Undo::DELETE,
1429 selection.start.par(), undoendpar);
1431 // there are two cases: cut only within one paragraph or
1432 // more than one paragraph
1433 if (selection.start.par() == selection.end.par()) {
1434 // only within one paragraph
1435 endpar = selection.end.par();
1436 int pos = selection.end.pos();
1437 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1438 selection.start.pos(), pos,
1439 bv()->buffer()->params.textclass,
1441 selection.end.pos(pos);
1443 endpar = selection.end.par();
1444 int pos = selection.end.pos();
1445 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1446 selection.start.pos(), pos,
1447 bv()->buffer()->params.textclass,
1450 selection.end.par(endpar);
1451 selection.end.pos(pos);
1452 cursor.pos(selection.end.pos());
1454 endpar = endpar->next();
1456 // sometimes necessary
1458 selection.start.par()->stripLeadingSpaces();
1460 redoParagraphs(selection.start, endpar);
1462 // cutSelection can invalidate the cursor so we need to set
1464 // we prefer the end for when tracking changes
1465 cursor = selection.end;
1467 // need a valid cursor. (Lgb)
1470 setCursor(cursor.par(), cursor.pos());
1471 selection.cursor = cursor;
1476 void LyXText::copySelection()
1478 // stuff the selection onto the X clipboard, from an explicit copy request
1479 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1481 // this doesnt make sense, if there is no selection
1482 if (!selection.set())
1485 // ok we have a selection. This is always between selection.start
1486 // and sel_end cursor
1488 // copy behind a space if there is one
1489 while (selection.start.par()->size() > selection.start.pos()
1490 && selection.start.par()->isLineSeparator(selection.start.pos())
1491 && (selection.start.par() != selection.end.par()
1492 || selection.start.pos() < selection.end.pos()))
1493 selection.start.pos(selection.start.pos() + 1);
1495 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1496 selection.start.pos(), selection.end.pos(),
1497 bv()->buffer()->params.textclass);
1501 void LyXText::pasteSelection()
1503 // this does not make sense, if there is nothing to paste
1504 if (!CutAndPaste::checkPastePossible())
1507 setUndo(bv(), Undo::INSERT,
1508 cursor.par(), cursor.par()->next());
1511 Paragraph * actpar = cursor.par();
1512 int pos = cursor.pos();
1514 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1515 bv()->buffer()->params.textclass);
1517 redoParagraphs(cursor, endpar);
1519 setCursor(cursor.par(), cursor.pos());
1522 selection.cursor = cursor;
1523 setCursor(actpar, pos);
1529 void LyXText::setSelectionRange(lyx::pos_type length)
1534 selection.cursor = cursor;
1541 // simple replacing. The font of the first selected character is used
1542 void LyXText::replaceSelectionWithString(string const & str)
1544 setCursorParUndo(bv());
1547 if (!selection.set()) { // create a dummy selection
1548 selection.end = cursor;
1549 selection.start = cursor;
1552 // Get font setting before we cut
1553 pos_type pos = selection.end.pos();
1554 LyXFont const font = selection.start.par()
1555 ->getFontSettings(bv()->buffer()->params,
1556 selection.start.pos());
1558 // Insert the new string
1559 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1560 selection.end.par()->insertChar(pos, (*cit), font);
1564 // Cut the selection
1565 cutSelection(true, false);
1571 // needed to insert the selection
1572 void LyXText::insertStringAsLines(string const & str)
1574 Paragraph * par = cursor.par();
1575 pos_type pos = cursor.pos();
1576 Paragraph * endpar = cursor.par()->next();
1578 setCursorParUndo(bv());
1580 // only to be sure, should not be neccessary
1583 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1585 redoParagraphs(cursor, endpar);
1586 setCursor(cursor.par(), cursor.pos());
1587 selection.cursor = cursor;
1588 setCursor(par, pos);
1593 // turns double-CR to single CR, others where converted into one
1594 // blank. Then InsertStringAsLines is called
1595 void LyXText::insertStringAsParagraphs(string const & str)
1597 string linestr(str);
1598 bool newline_inserted = false;
1599 for (string::size_type i = 0; i < linestr.length(); ++i) {
1600 if (linestr[i] == '\n') {
1601 if (newline_inserted) {
1602 // we know that \r will be ignored by
1603 // InsertStringA. Of course, it is a dirty
1604 // trick, but it works...
1605 linestr[i - 1] = '\r';
1609 newline_inserted = true;
1611 } else if (IsPrintable(linestr[i])) {
1612 newline_inserted = false;
1615 insertStringAsLines(linestr);
1619 void LyXText::checkParagraph(Paragraph * par,
1622 LyXCursor tmpcursor;
1626 Row * row = getRow(par, pos, y);
1628 // is there a break one row above
1629 if (row->previous() && row->previous()->par() == row->par()) {
1630 z = rowBreakPoint(*row->previous());
1631 if (z >= row->pos()) {
1632 // set the dimensions of the row above
1633 y -= row->previous()->height();
1636 breakAgain(row->previous());
1638 // set the cursor again. Otherwise
1639 // dangling pointers are possible
1640 setCursor(cursor.par(), cursor.pos(),
1641 false, cursor.boundary());
1642 selection.cursor = cursor;
1647 int const tmpheight = row->height();
1648 pos_type const tmplast = row->lastPos();
1651 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1652 postRowPaint(row, y);
1657 // check the special right address boxes
1658 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1665 redoDrawingOfParagraph(tmpcursor);
1668 // set the cursor again. Otherwise dangling pointers are possible
1669 // also set the selection
1671 if (selection.set()) {
1673 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1674 false, selection.cursor.boundary());
1675 selection.cursor = cursor;
1676 setCursorIntern(selection.start.par(),
1677 selection.start.pos(),
1678 false, selection.start.boundary());
1679 selection.start = cursor;
1680 setCursorIntern(selection.end.par(),
1681 selection.end.pos(),
1682 false, selection.end.boundary());
1683 selection.end = cursor;
1684 setCursorIntern(last_sel_cursor.par(),
1685 last_sel_cursor.pos(),
1686 false, last_sel_cursor.boundary());
1687 last_sel_cursor = cursor;
1690 setCursorIntern(cursor.par(), cursor.pos(),
1691 false, cursor.boundary());
1695 // returns false if inset wasn't found
1696 bool LyXText::updateInset(Inset * inset)
1698 // first check the current paragraph
1699 int pos = cursor.par()->getPositionOfInset(inset);
1701 checkParagraph(cursor.par(), pos);
1705 // check every paragraph
1707 Paragraph * par = ownerParagraph();
1709 pos = par->getPositionOfInset(inset);
1711 checkParagraph(par, pos);
1721 bool LyXText::setCursor(Paragraph * par,
1723 bool setfont, bool boundary)
1725 LyXCursor old_cursor = cursor;
1726 setCursorIntern(par, pos, setfont, boundary);
1727 return deleteEmptyParagraphMechanism(old_cursor);
1731 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1732 pos_type pos, bool boundary)
1738 cur.boundary(boundary);
1740 // get the cursor y position in text
1742 Row * row = getRow(par, pos, y);
1743 Row * old_row = row;
1745 // if we are before the first char of this row and are still in the
1746 // same paragraph and there is a previous row then put the cursor on
1747 // the end of the previous row
1748 cur.iy(y + row->baseline());
1750 if (row->previous() && pos &&
1751 row->previous()->par() == row->par() &&
1752 pos < par->size() &&
1753 par->getChar(pos) == Paragraph::META_INSET &&
1754 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1756 row = row->previous();
1761 // y is now the beginning of the cursor row
1762 y += row->baseline();
1763 // y is now the cursor baseline
1766 pos_type last = old_row->lastPrintablePos();
1768 // None of these should happen, but we're scaredy-cats
1769 if (pos > par->size()) {
1770 lyxerr << "dont like 1 please report" << endl;
1773 } else if (pos > last + 1) {
1774 lyxerr << "dont like 2 please report" << endl;
1775 // This shouldn't happen.
1778 } else if (pos < row->pos()) {
1779 lyxerr << "dont like 3 please report" << endl;
1784 // now get the cursors x position
1785 float x = getCursorX(row, pos, last, boundary);
1788 if (old_row != row) {
1789 x = getCursorX(old_row, pos, last, boundary);
1793 //if the cursor is in a visible row, anchor to it
1795 if (topy < y && y < topy + bv()->workHeight())
1800 float LyXText::getCursorX(Row * row,
1801 pos_type pos, pos_type last, bool boundary) const
1803 pos_type cursor_vpos = 0;
1805 float fill_separator;
1807 float fill_label_hfill;
1808 // This call HAS to be here because of the BidiTables!!!
1809 prepareToPrint(row, x, fill_separator, fill_hfill,
1812 if (last < row->pos())
1813 cursor_vpos = row->pos();
1814 else if (pos > last && !boundary)
1815 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1816 ? row->pos() : last + 1;
1817 else if (pos > row->pos() &&
1818 (pos > last || boundary))
1819 /// Place cursor after char at (logical) position pos - 1
1820 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1821 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1823 /// Place cursor before char at (logical) position pos
1824 cursor_vpos = (bidi_level(pos) % 2 == 0)
1825 ? log2vis(pos) : log2vis(pos) + 1;
1827 pos_type body_pos = row->par()->beginningOfBody();
1828 if ((body_pos > 0) &&
1829 ((body_pos-1 > last) ||
1830 !row->par()->isLineSeparator(body_pos - 1)))
1833 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1834 pos_type pos = vis2log(vpos);
1835 if (body_pos > 0 && pos == body_pos - 1) {
1836 x += fill_label_hfill +
1837 font_metrics::width(
1838 row->par()->layout()->labelsep,
1839 getLabelFont(bv()->buffer(),
1841 if (row->par()->isLineSeparator(body_pos - 1))
1843 row->par(), body_pos - 1);
1845 if (row->hfillExpansion(pos)) {
1846 x += singleWidth(row->par(), pos);
1847 if (pos >= body_pos)
1850 x += fill_label_hfill;
1851 } else if (row->par()->isSeparator(pos)) {
1852 x += singleWidth(row->par(), pos);
1853 if (pos >= body_pos)
1854 x += fill_separator;
1856 x += singleWidth(row->par(), pos);
1862 void LyXText::setCursorIntern(Paragraph * par,
1863 pos_type pos, bool setfont, bool boundary)
1865 InsetText * it = static_cast<InsetText *>(par->inInset());
1867 if (it != inset_owner) {
1868 lyxerr[Debug::INSETS] << "InsetText is " << it
1870 << "inset_owner is "
1871 << inset_owner << endl;
1872 #ifdef WITH_WARNINGS
1873 #warning I believe this code is wrong. (Lgb)
1874 #warning Jürgen, have a look at this. (Lgb)
1875 #warning Hmmm, I guess you are right but we
1876 #warning should verify when this is needed
1878 // Jürgen, would you like to have a look?
1879 // I guess we need to move the outer cursor
1880 // and open and lock the inset (bla bla bla)
1881 // stuff I don't know... so can you have a look?
1883 // I moved the lyxerr stuff in here so we can see if
1884 // this is actually really needed and where!
1886 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1891 setCursor(cursor, par, pos, boundary);
1897 void LyXText::setCurrentFont()
1899 pos_type pos = cursor.pos();
1900 if (cursor.boundary() && pos > 0)
1904 if (pos == cursor.par()->size())
1906 else // potentional bug... BUG (Lgb)
1907 if (cursor.par()->isSeparator(pos)) {
1908 if (pos > cursor.row()->pos() &&
1909 bidi_level(pos) % 2 ==
1910 bidi_level(pos - 1) % 2)
1912 else if (pos + 1 < cursor.par()->size())
1918 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1919 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1921 if (cursor.pos() == cursor.par()->size() &&
1922 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1923 !cursor.boundary()) {
1924 Language const * lang =
1925 cursor.par()->getParLanguage(bv()->buffer()->params);
1926 current_font.setLanguage(lang);
1927 current_font.setNumber(LyXFont::OFF);
1928 real_current_font.setLanguage(lang);
1929 real_current_font.setNumber(LyXFont::OFF);
1934 // returns the column near the specified x-coordinate of the row
1935 // x is set to the real beginning of this column
1937 LyXText::getColumnNearX(Row * row, int & x,
1938 bool & boundary) const
1941 float fill_separator;
1943 float fill_label_hfill;
1945 prepareToPrint(row, tmpx, fill_separator,
1946 fill_hfill, fill_label_hfill);
1948 pos_type vc = row->pos();
1949 pos_type last = row->lastPrintablePos();
1952 LyXLayout_ptr const & layout = row->par()->layout();
1954 bool left_side = false;
1956 pos_type body_pos = row->par()->beginningOfBody();
1957 float last_tmpx = tmpx;
1960 (body_pos - 1 > last ||
1961 !row->par()->isLineSeparator(body_pos - 1)))
1964 // check for empty row
1965 if (!row->par()->size()) {
1970 while (vc <= last && tmpx <= x) {
1973 if (body_pos > 0 && c == body_pos-1) {
1974 tmpx += fill_label_hfill +
1975 font_metrics::width(layout->labelsep,
1976 getLabelFont(bv()->buffer(), row->par()));
1977 if (row->par()->isLineSeparator(body_pos - 1))
1978 tmpx -= singleWidth(row->par(), body_pos-1);
1981 if (row->hfillExpansion(c)) {
1982 tmpx += singleWidth(row->par(), c);
1986 tmpx += fill_label_hfill;
1987 } else if (row->par()->isSeparator(c)) {
1988 tmpx += singleWidth(row->par(), c);
1990 tmpx+= fill_separator;
1992 tmpx += singleWidth(row->par(), c);
1997 if ((tmpx + last_tmpx) / 2 > x) {
2002 if (vc > last + 1) // This shouldn't happen.
2006 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
2007 // some speedup if rtl_support=false
2008 && (!row->next() || row->next()->par() != row->par());
2009 bool const rtl = (lastrow)
2010 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
2011 : false; // If lastrow is false, we don't need to compute
2012 // the value of rtl.
2015 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
2016 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
2018 else if (vc == row->pos()) {
2020 if (bidi_level(c) % 2 == 1)
2023 c = vis2log(vc - 1);
2024 bool const rtl = (bidi_level(c) % 2 == 1);
2025 if (left_side == rtl) {
2027 boundary = isBoundary(bv()->buffer(), row->par(), c);
2031 if (row->pos() <= last && c > last
2032 && row->par()->isNewline(last)) {
2033 if (bidi_level(last) % 2 == 0)
2034 tmpx -= singleWidth(row->par(), last);
2036 tmpx += singleWidth(row->par(), last);
2046 void LyXText::setCursorFromCoordinates(int x, int y)
2048 LyXCursor old_cursor = cursor;
2050 setCursorFromCoordinates(cursor, x, y);
2052 deleteEmptyParagraphMechanism(old_cursor);
2059 * return true if the cursor given is at the end of a row,
2060 * and the next row is filled by an inset that spans an entire
2063 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2066 Row const & next = *row.next();
2068 if (next.pos() != cur.pos() || next.par() != cur.par())
2070 if (!cur.par()->isInset(cur.pos()))
2072 Inset const * inset = cur.par()->getInset(cur.pos());
2073 if (inset->needFullRow() || inset->display())
2080 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2083 // Get the row first.
2085 Row * row = getRowNearY(y);
2087 pos_type const column = getColumnNearX(row, x, bound);
2088 cur.par(row->par());
2089 cur.pos(row->pos() + column);
2091 cur.y(y + row->baseline());
2094 if (beforeFullRowInset(*row, cur)) {
2095 pos_type last = row->lastPrintablePos();
2096 float x = getCursorX(row->next(), cur.pos(), last, bound);
2098 cur.iy(y + row->height() + row->next()->baseline());
2099 cur.irow(row->next());
2105 cur.boundary(bound);
2109 void LyXText::cursorLeft(bool internal)
2111 if (cursor.pos() > 0) {
2112 bool boundary = cursor.boundary();
2113 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2114 if (!internal && !boundary &&
2115 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2116 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2117 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2118 Paragraph * par = cursor.par()->previous();
2119 setCursor(par, par->size());
2124 void LyXText::cursorRight(bool internal)
2126 if (!internal && cursor.boundary() &&
2127 !cursor.par()->isNewline(cursor.pos()))
2128 setCursor(cursor.par(), cursor.pos(), true, false);
2129 else if (cursor.pos() < cursor.par()->size()) {
2130 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2132 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2133 setCursor(cursor.par(), cursor.pos(), true, true);
2134 } else if (cursor.par()->next())
2135 setCursor(cursor.par()->next(), 0);
2139 void LyXText::cursorUp(bool selecting)
2142 int x = cursor.x_fix();
2143 int y = cursor.y() - cursor.row()->baseline() - 1;
2144 setCursorFromCoordinates(x, y);
2147 int y1 = cursor.iy() - topy;
2150 Inset * inset_hit = checkInsetHit(x, y1);
2151 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2152 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2156 setCursorFromCoordinates(bv(), cursor.x_fix(),
2157 cursor.y() - cursor.row()->baseline() - 1);
2162 void LyXText::cursorDown(bool selecting)
2165 int x = cursor.x_fix();
2166 int y = cursor.y() - cursor.row()->baseline() +
2167 cursor.row()->height() + 1;
2168 setCursorFromCoordinates(x, y);
2169 if (!selecting && cursor.row() == cursor.irow()) {
2171 int y1 = cursor.iy() - topy;
2174 Inset * inset_hit = checkInsetHit(x, y1);
2175 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2176 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2180 setCursorFromCoordinates(bv(), cursor.x_fix(),
2181 cursor.y() - cursor.row()->baseline()
2182 + cursor.row()->height() + 1);
2187 void LyXText::cursorUpParagraph()
2189 if (cursor.pos() > 0) {
2190 setCursor(cursor.par(), 0);
2192 else if (cursor.par()->previous()) {
2193 setCursor(cursor.par()->previous(), 0);
2198 void LyXText::cursorDownParagraph()
2200 if (cursor.par()->next()) {
2201 setCursor(cursor.par()->next(), 0);
2203 setCursor(cursor.par(), cursor.par()->size());
2207 // fix the cursor `cur' after a characters has been deleted at `where'
2208 // position. Called by deleteEmptyParagraphMechanism
2209 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2210 LyXCursor const & where)
2212 // if cursor is not in the paragraph where the delete occured,
2214 if (cur.par() != where.par())
2217 // if cursor position is after the place where the delete occured,
2219 if (cur.pos() > where.pos())
2220 cur.pos(cur.pos()-1);
2222 // check also if we don't want to set the cursor on a spot behind the
2223 // pagragraph because we erased the last character.
2224 if (cur.pos() > cur.par()->size())
2225 cur.pos(cur.par()->size());
2227 // recompute row et al. for this cursor
2228 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2232 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2234 // Would be wrong to delete anything if we have a selection.
2235 if (selection.set())
2238 // We allow all kinds of "mumbo-jumbo" when freespacing.
2239 if (old_cursor.par()->layout()->free_spacing
2240 || old_cursor.par()->isFreeSpacing()) {
2244 /* Ok I'll put some comments here about what is missing.
2245 I have fixed BackSpace (and thus Delete) to not delete
2246 double-spaces automagically. I have also changed Cut,
2247 Copy and Paste to hopefully do some sensible things.
2248 There are still some small problems that can lead to
2249 double spaces stored in the document file or space at
2250 the beginning of paragraphs. This happens if you have
2251 the cursor betwenn to spaces and then save. Or if you
2252 cut and paste and the selection have a space at the
2253 beginning and then save right after the paste. I am
2254 sure none of these are very hard to fix, but I will
2255 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2256 that I can get some feedback. (Lgb)
2259 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2260 // delete the LineSeparator.
2263 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2264 // delete the LineSeparator.
2267 // If the pos around the old_cursor were spaces, delete one of them.
2268 if (old_cursor.par() != cursor.par()
2269 || old_cursor.pos() != cursor.pos()) {
2270 // Only if the cursor has really moved
2272 if (old_cursor.pos() > 0
2273 && old_cursor.pos() < old_cursor.par()->size()
2274 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2275 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2276 old_cursor.par()->erase(old_cursor.pos() - 1);
2277 redoParagraphs(old_cursor, old_cursor.par()->next());
2279 #ifdef WITH_WARNINGS
2280 #warning This will not work anymore when we have multiple views of the same buffer
2281 // In this case, we will have to correct also the cursors held by
2282 // other bufferviews. It will probably be easier to do that in a more
2283 // automated way in LyXCursor code. (JMarc 26/09/2001)
2285 // correct all cursors held by the LyXText
2286 fixCursorAfterDelete(cursor, old_cursor);
2287 fixCursorAfterDelete(selection.cursor,
2289 fixCursorAfterDelete(selection.start,
2291 fixCursorAfterDelete(selection.end, old_cursor);
2292 fixCursorAfterDelete(last_sel_cursor,
2294 fixCursorAfterDelete(toggle_cursor, old_cursor);
2295 fixCursorAfterDelete(toggle_end_cursor,
2301 // don't delete anything if this is the ONLY paragraph!
2302 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2305 // Do not delete empty paragraphs with keepempty set.
2306 if (old_cursor.par()->layout()->keepempty)
2309 // only do our magic if we changed paragraph
2310 if (old_cursor.par() == cursor.par())
2313 // record if we have deleted a paragraph
2314 // we can't possibly have deleted a paragraph before this point
2315 bool deleted = false;
2317 if ((old_cursor.par()->empty()
2318 || (old_cursor.par()->size() == 1
2319 && old_cursor.par()->isLineSeparator(0)))) {
2320 // ok, we will delete anything
2321 LyXCursor tmpcursor;
2325 if (old_cursor.row()->previous()) {
2326 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2327 - old_cursor.row()->previous()->height());
2329 cursor = old_cursor; // that undo can restore the right cursor position
2330 Paragraph * endpar = old_cursor.par()->next();
2331 if (endpar && endpar->getDepth()) {
2332 while (endpar && endpar->getDepth()) {
2333 endpar = endpar->next();
2336 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2340 removeRow(old_cursor.row());
2341 if (ownerParagraph() == old_cursor.par()) {
2342 ownerParagraph(ownerParagraph()->next());
2345 delete old_cursor.par();
2347 /* Breakagain the next par. Needed because of
2348 * the parindent that can occur or dissappear.
2349 * The next row can change its height, if
2350 * there is another layout before */
2351 if (refresh_row->next()) {
2352 breakAgain(refresh_row->next());
2355 setHeightOfRow(refresh_row);
2357 Row * nextrow = old_cursor.row()->next();
2358 const_cast<LyXText *>(this)->postPaint(
2359 old_cursor.y() - old_cursor.row()->baseline());
2362 cursor = old_cursor; // that undo can restore the right cursor position
2363 Paragraph * endpar = old_cursor.par()->next();
2364 if (endpar && endpar->getDepth()) {
2365 while (endpar && endpar->getDepth()) {
2366 endpar = endpar->next();
2369 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2373 removeRow(old_cursor.row());
2375 if (ownerParagraph() == old_cursor.par()) {
2376 ownerParagraph(ownerParagraph()->next());
2379 delete old_cursor.par();
2381 /* Breakagain the next par. Needed because of
2382 the parindent that can occur or dissappear.
2383 The next row can change its height, if
2384 there is another layout before */
2386 breakAgain(nextrow);
2392 setCursorIntern(cursor.par(), cursor.pos());
2394 if (selection.cursor.par() == old_cursor.par()
2395 && selection.cursor.pos() == old_cursor.pos()) {
2396 // correct selection
2397 selection.cursor = cursor;
2401 if (old_cursor.par()->stripLeadingSpaces()) {
2402 redoParagraphs(old_cursor,
2403 old_cursor.par()->next());
2405 setCursorIntern(cursor.par(), cursor.pos());
2406 selection.cursor = cursor;
2413 Paragraph * LyXText::ownerParagraph() const
2416 return inset_owner->paragraph();
2418 return &*(bv_owner->buffer()->paragraphs.begin());
2422 void LyXText::ownerParagraph(Paragraph * p) const
2425 inset_owner->paragraph(p);
2427 bv_owner->buffer()->paragraphs.set(p);
2432 void LyXText::ownerParagraph(int id, Paragraph * p) const
2434 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2435 if (op && op->inInset()) {
2436 static_cast<InsetText *>(op->inInset())->paragraph(p);
2443 LyXText::refresh_status LyXText::refreshStatus() const
2445 return refresh_status_;
2449 void LyXText::clearPaint()
2451 refresh_status_ = REFRESH_NONE;
2457 void LyXText::postPaint(int start_y)
2459 refresh_status old = refresh_status_;
2461 refresh_status_ = REFRESH_AREA;
2464 if (old != REFRESH_NONE && refresh_y < start_y)
2467 refresh_y = start_y;
2472 // We are an inset's lyxtext. Tell the top-level lyxtext
2473 // it needs to update the row we're in.
2474 LyXText * t = bv()->text;
2475 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2479 // FIXME: we should probably remove this y parameter,
2480 // make refresh_y be 0, and use row->y etc.
2481 void LyXText::postRowPaint(Row * row, int start_y)
2483 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2484 refresh_status_ = REFRESH_AREA;
2487 refresh_y = start_y;
2490 if (refresh_status_ == REFRESH_AREA)
2493 refresh_status_ = REFRESH_ROW;
2499 // We are an inset's lyxtext. Tell the top-level lyxtext
2500 // it needs to update the row we're in.
2501 LyXText * t = bv()->text;
2502 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2506 bool LyXText::isInInset() const
2508 // Sub-level has non-null bv owner and
2509 // non-null inset owner.
2510 return inset_owner != 0 && bv_owner != 0;
2514 int defaultRowHeight()
2516 LyXFont const font(LyXFont::ALL_SANE);
2517 return int(font_metrics::maxAscent(font)
2518 + font_metrics::maxDescent(font) * 1.5);