1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
33 #include "insets/inseterror.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetspecialchar.h"
36 #include "insets/insettext.h"
37 #include "insets/insetfloat.h"
38 #include "insets/insetwrap.h"
40 #include "support/LAssert.h"
41 #include "support/textutils.h"
42 #include "support/lstrings.h"
44 #include "BoostFormat.h"
54 LyXText::LyXText(BufferView * bv)
55 : height(0), width(0), anchor_row_(0), anchor_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), need_break_row(0),
64 LyXText::LyXText(BufferView * bv, InsetText * inset)
65 : height(0), width(0), anchor_row_(0), anchor_row_offset_(0),
66 inset_owner(inset), the_locking_inset(0), need_break_row(0),
74 void LyXText::init(BufferView * bview, bool reinit)
80 copylayouttype.erase();
83 } else if (!rowlist_.empty())
86 Paragraph * par = ownerParagraph();
87 current_font = getFont(bview->buffer(), par, 0);
90 insertParagraph(par, rowlist_.end());
93 setCursorIntern(rowlist_.begin()->par(), 0);
94 selection.cursor = cursor;
102 LyXFont const realizeFont(LyXFont const & font,
106 LyXTextClass const & tclass = buf->params.getLyXTextClass();
107 LyXFont tmpfont(font);
108 Paragraph::depth_type par_depth = par->getDepth();
110 // Resolve against environment font information
111 while (par && par_depth && !tmpfont.resolved()) {
112 par = par->outerHook();
114 tmpfont.realize(par->layout()->font);
115 par_depth = par->getDepth();
119 tmpfont.realize(tclass.defaultfont());
127 // Gets the fully instantiated font at a given position in a paragraph
128 // Basically the same routine as Paragraph::getFont() in paragraph.C.
129 // The difference is that this one is used for displaying, and thus we
130 // are allowed to make cosmetic improvements. For instance make footnotes
132 // If position is -1, we get the layout font of the paragraph.
133 // If position is -2, we get the font of the manual label of the paragraph.
134 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
137 lyx::Assert(pos >= 0);
139 LyXLayout_ptr const & layout = par->layout();
141 // We specialize the 95% common case:
142 if (!par->getDepth()) {
143 if (layout->labeltype == LABEL_MANUAL
144 && pos < par->beginningOfBody()) {
146 LyXFont f = par->getFontSettings(buf->params, pos);
148 par->inInset()->getDrawFont(f);
149 return f.realize(layout->reslabelfont);
151 LyXFont f = par->getFontSettings(buf->params, pos);
153 par->inInset()->getDrawFont(f);
154 return f.realize(layout->resfont);
158 // The uncommon case need not be optimized as much
162 if (pos < par->beginningOfBody()) {
164 layoutfont = layout->labelfont;
167 layoutfont = layout->font;
170 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
171 tmpfont.realize(layoutfont);
174 par->inInset()->getDrawFont(tmpfont);
176 return realizeFont(tmpfont, buf, par);
180 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
182 LyXLayout_ptr const & layout = par->layout();
184 if (!par->getDepth()) {
185 return layout->resfont;
188 return realizeFont(layout->font, buf, par);
192 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
194 LyXLayout_ptr const & layout = par->layout();
196 if (!par->getDepth()) {
197 return layout->reslabelfont;
200 return realizeFont(layout->labelfont, buf, par);
204 void LyXText::setCharFont(Paragraph * par,
205 pos_type pos, LyXFont const & fnt,
208 Buffer const * buf = bv()->buffer();
209 LyXFont font = getFont(buf, par, pos);
210 font.update(fnt, buf->params.language, toggleall);
211 // Let the insets convert their font
212 if (par->isInset(pos)) {
213 Inset * inset = par->getInset(pos);
214 if (isEditableInset(inset)) {
215 UpdatableInset * uinset =
216 static_cast<UpdatableInset *>(inset);
217 uinset->setFont(bv(), fnt, toggleall, true);
221 // Plug thru to version below:
222 setCharFont(buf, par, pos, font);
226 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
227 pos_type pos, LyXFont const & fnt)
231 LyXTextClass const & tclass = buf->params.getLyXTextClass();
232 LyXLayout_ptr const & layout = par->layout();
234 // Get concrete layout font to reduce against
237 if (pos < par->beginningOfBody())
238 layoutfont = layout->labelfont;
240 layoutfont = layout->font;
242 // Realize against environment font information
243 if (par->getDepth()) {
244 Paragraph * tp = par;
245 while (!layoutfont.resolved() && tp && tp->getDepth()) {
246 tp = tp->outerHook();
248 layoutfont.realize(tp->layout()->font);
252 layoutfont.realize(tclass.defaultfont());
254 // Now, reduce font against full layout font
255 font.reduce(layoutfont);
257 par->setFont(pos, font);
261 // removes the row and reset the touched counters
262 void LyXText::removeRow(Row * row)
266 Row * row_prev = row->previous();
267 Row * row_next = row->next();
268 int const row_height = row->height();
270 /* FIXME: when we cache the bview, this should just
271 * become a postPaint(), I think */
272 if (refresh_row == row) {
273 refresh_row = row_prev ? row_prev : row_next;
274 // what about refresh_y
277 if (anchor_row_ == row) {
279 anchor_row_ = row_prev;
280 anchor_row_offset_ += row_prev->height();
282 anchor_row_ = row_next;
283 anchor_row_offset_ -= row_height;
287 // the text becomes smaller
288 height -= row_height;
294 // remove all following rows of the paragraph of the specified row.
295 void LyXText::removeParagraph(Row * row)
297 Paragraph * tmppar = row->par();
301 while (row && row->par() == tmppar) {
302 tmprow = row->next();
309 void LyXText::insertParagraph(Paragraph * par, RowList::iterator rowit)
311 // insert a new row, starting at position 0
312 RowList::iterator rit = rowlist_.insert(rowit, new Row(par, 0));
314 // and now append the whole paragraph before the new row
315 appendParagraph(rit);
319 Inset * LyXText::getInset() const
321 if (cursor.pos() < cursor.par()->size()
322 && cursor.par()->isInset(cursor.pos())) {
323 return cursor.par()->getInset(cursor.pos());
329 void LyXText::toggleInset()
331 Inset * inset = getInset();
332 // is there an editable inset at cursor position?
333 if (!isEditableInset(inset)) {
334 // No, try to see if we are inside a collapsable inset
335 if (inset_owner && inset_owner->owner()
336 && inset_owner->owner()->isOpen()) {
337 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
338 inset_owner->owner()->close(bv());
339 bv()->getLyXText()->cursorRight(bv());
343 //bv()->owner()->message(inset->editMessage());
345 // do we want to keep this?? (JMarc)
346 if (!isHighlyEditableInset(inset))
347 setCursorParUndo(bv());
349 if (inset->isOpen()) {
355 bv()->updateInset(inset);
359 /* used in setlayout */
360 // Asger is not sure we want to do this...
361 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
364 LyXLayout_ptr const & layout = par.layout();
367 for (pos_type pos = 0; pos < par.size(); ++pos) {
368 if (pos < par.beginningOfBody())
369 layoutfont = layout->labelfont;
371 layoutfont = layout->font;
373 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
374 tmpfont.reduce(layoutfont);
375 par.setFont(pos, tmpfont);
380 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
381 LyXCursor & send_cur,
382 string const & layout)
384 Paragraph * endpar = send_cur.par()->next();
385 Paragraph * undoendpar = endpar;
387 if (endpar && endpar->getDepth()) {
388 while (endpar && endpar->getDepth()) {
389 endpar = endpar->next();
393 endpar = endpar->next(); // because of parindents etc.
396 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
398 // ok we have a selection. This is always between sstart_cur
399 // and sel_end cursor
401 Paragraph * par = sstart_cur.par();
402 Paragraph * epar = send_cur.par()->next();
404 LyXLayout_ptr const & lyxlayout =
405 bv()->buffer()->params.getLyXTextClass()[layout];
408 par->applyLayout(lyxlayout);
409 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
410 Paragraph * fppar = par;
411 fppar->params().spaceTop(lyxlayout->fill_top ?
412 VSpace(VSpace::VFILL)
413 : VSpace(VSpace::NONE));
414 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
415 VSpace(VSpace::VFILL)
416 : VSpace(VSpace::NONE));
417 if (lyxlayout->margintype == MARGIN_MANUAL)
418 par->setLabelWidthString(lyxlayout->labelstring());
421 } while (par != epar);
427 // set layout over selection and make a total rebreak of those paragraphs
428 void LyXText::setLayout(string const & layout)
430 LyXCursor tmpcursor = cursor; /* store the current cursor */
432 // if there is no selection just set the layout
433 // of the current paragraph */
434 if (!selection.set()) {
435 selection.start = cursor; // dummy selection
436 selection.end = cursor;
438 Paragraph * endpar = setLayout(cursor, selection.start,
439 selection.end, layout);
440 redoParagraphs(selection.start, endpar);
442 // we have to reset the selection, because the
443 // geometry could have changed
444 setCursor(selection.start.par(),
445 selection.start.pos(), false);
446 selection.cursor = cursor;
447 setCursor(selection.end.par(), selection.end.pos(), false);
451 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
455 // increment depth over selection and
456 // make a total rebreak of those paragraphs
457 void LyXText::incDepth()
459 // If there is no selection, just use the current paragraph
460 if (!selection.set()) {
461 selection.start = cursor; // dummy selection
462 selection.end = cursor;
465 // We end at the next paragraph with depth 0
466 Paragraph * endpar = selection.end.par()->next();
468 Paragraph * undoendpar = endpar;
470 if (endpar && endpar->getDepth()) {
471 while (endpar && endpar->getDepth()) {
472 endpar = endpar->next();
476 endpar = endpar->next(); // because of parindents etc.
479 setUndo(bv(), Undo::EDIT,
480 selection.start.par(), undoendpar);
482 LyXCursor tmpcursor = cursor; // store the current cursor
484 // ok we have a selection. This is always between sel_start_cursor
485 // and sel_end cursor
486 cursor = selection.start;
489 // NOTE: you can't change the depth of a bibliography entry
490 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
491 Paragraph * prev = cursor.par()->previous();
494 if (cursor.par()->getDepth()
495 < prev->getMaxDepthAfter()) {
496 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
500 if (cursor.par() == selection.end.par())
502 cursor.par(cursor.par()->next());
505 redoParagraphs(selection.start, endpar);
507 // we have to reset visual the selection because the
508 // geometry could have changed
509 setCursor(selection.start.par(), selection.start.pos());
510 selection.cursor = cursor;
511 setCursor(selection.end.par(), selection.end.pos());
514 setCursor(tmpcursor.par(), tmpcursor.pos());
518 // decrement depth over selection and
519 // make a total rebreak of those paragraphs
520 void LyXText::decDepth()
522 // if there is no selection just set the layout
523 // of the current paragraph
524 if (!selection.set()) {
525 selection.start = cursor; // dummy selection
526 selection.end = cursor;
528 Paragraph * endpar = selection.end.par()->next();
529 Paragraph * undoendpar = endpar;
531 if (endpar && endpar->getDepth()) {
532 while (endpar && endpar->getDepth()) {
533 endpar = endpar->next();
537 endpar = endpar->next(); // because of parindents etc.
540 setUndo(bv(), Undo::EDIT,
541 selection.start.par(), undoendpar);
543 LyXCursor tmpcursor = cursor; // store the current cursor
545 // ok we have a selection. This is always between sel_start_cursor
546 // and sel_end cursor
547 cursor = selection.start;
550 if (cursor.par()->params().depth()) {
551 cursor.par()->params()
552 .depth(cursor.par()->params().depth() - 1);
554 if (cursor.par() == selection.end.par()) {
557 cursor.par(cursor.par()->next());
560 redoParagraphs(selection.start, endpar);
562 // we have to reset the visual selection because the
563 // geometry could have changed
564 setCursor(selection.start.par(), selection.start.pos());
565 selection.cursor = cursor;
566 setCursor(selection.end.par(), selection.end.pos());
569 setCursor(tmpcursor.par(), tmpcursor.pos());
573 // set font over selection and make a total rebreak of those paragraphs
574 void LyXText::setFont(LyXFont const & font, bool toggleall)
576 // if there is no selection just set the current_font
577 if (!selection.set()) {
578 // Determine basis font
580 if (cursor.pos() < cursor.par()->beginningOfBody()) {
581 layoutfont = getLabelFont(bv()->buffer(),
584 layoutfont = getLayoutFont(bv()->buffer(),
587 // Update current font
588 real_current_font.update(font,
589 bv()->buffer()->params.language,
592 // Reduce to implicit settings
593 current_font = real_current_font;
594 current_font.reduce(layoutfont);
595 // And resolve it completely
596 real_current_font.realize(layoutfont);
601 LyXCursor tmpcursor = cursor; // store the current cursor
603 // ok we have a selection. This is always between sel_start_cursor
604 // and sel_end cursor
606 setUndo(bv(), Undo::EDIT,
607 selection.start.par(), selection.end.par()->next());
609 cursor = selection.start;
610 while (cursor.par() != selection.end.par() ||
611 cursor.pos() < selection.end.pos())
613 if (cursor.pos() < cursor.par()->size()) {
614 // an open footnote should behave like a closed one
615 setCharFont(cursor.par(), cursor.pos(),
617 cursor.pos(cursor.pos() + 1);
620 cursor.par(cursor.par()->next());
625 redoParagraphs(selection.start, selection.end.par()->next());
627 // we have to reset the selection, because the
628 // geometry could have changed, but we keep
629 // it for user convenience
630 setCursor(selection.start.par(), selection.start.pos());
631 selection.cursor = cursor;
632 setCursor(selection.end.par(), selection.end.pos());
634 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
635 tmpcursor.boundary());
639 void LyXText::redoHeightOfParagraph()
641 Row * tmprow = cursor.row();
642 int y = cursor.y() - tmprow->baseline();
644 setHeightOfRow(tmprow);
646 while (tmprow->previous()
647 && tmprow->previous()->par() == tmprow->par()) {
648 tmprow = tmprow->previous();
649 y -= tmprow->height();
650 setHeightOfRow(tmprow);
655 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
659 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
661 Row * tmprow = cur.row();
663 int y = cur.y() - tmprow->baseline();
664 setHeightOfRow(tmprow);
666 while (tmprow->previous()
667 && tmprow->previous()->par() == tmprow->par()) {
668 tmprow = tmprow->previous();
669 y -= tmprow->height();
673 setCursor(cur.par(), cur.pos());
677 // deletes and inserts again all paragaphs between the cursor
678 // and the specified par
679 // This function is needed after SetLayout and SetFont etc.
680 void LyXText::redoParagraphs(LyXCursor const & cur,
681 Paragraph const * endpar)
683 Row * tmprow = cur.row();
685 int y = cur.y() - tmprow->baseline();
687 Paragraph * first_phys_par = 0;
688 if (!tmprow->previous()) {
689 // a trick/hack for UNDO
690 // This is needed because in an UNDO/REDO we could have changed
691 // the ownerParagrah() so the paragraph inside the row is NOT
692 // my really first par anymore. Got it Lars ;) (Jug 20011206)
693 first_phys_par = ownerParagraph();
695 first_phys_par = tmprow->par();
697 // Find first row of this paragraph.
698 while (tmprow->previous()
699 && tmprow->previous()->par() == first_phys_par)
701 tmprow = tmprow->previous();
702 y -= tmprow->height();
706 Row * prevrow = tmprow->previous();
708 // Remove all the rows until we reach endpar
709 Paragraph * tmppar = 0;
711 tmppar = tmprow->next()->par();
712 while (tmprow->next() && tmppar != endpar) {
713 removeRow(tmprow->next());
714 if (tmprow->next()) {
715 tmppar = tmprow->next()->par();
721 // Remove the first of the paragraphs rows.
722 // This is because tmprow->previous() can be 0
723 Row * tmprow2 = tmprow;
724 tmprow = tmprow->previous();
727 // Reinsert the paragraphs.
728 tmppar = first_phys_par;
732 insertParagraph(tmppar, rowlist_.begin());
734 insertParagraph(tmppar, tmprow->next());
739 tmprow = &*rows().begin();
741 while (tmprow->next()
742 && tmprow->next()->par() == tmppar) {
743 tmprow = tmprow->next();
745 tmppar = tmppar->next();
747 } while (tmppar && tmppar != endpar);
749 // this is because of layout changes
751 setHeightOfRow(prevrow);
752 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
754 setHeightOfRow(&*rows().begin());
755 const_cast<LyXText *>(this)->postPaint(0);
758 if (tmprow && tmprow->next())
759 setHeightOfRow(tmprow->next());
764 void LyXText::fullRebreak()
766 if (rows().empty()) {
770 if (need_break_row) {
771 breakAgain(need_break_row);
778 // important for the screen
781 // the cursor set functions have a special mechanism. When they
782 // realize, that you left an empty paragraph, they will delete it.
783 // They also delete the corresponding row
785 // need the selection cursor:
786 void LyXText::setSelection()
788 bool const lsel = selection.set();
790 if (!selection.set()) {
791 last_sel_cursor = selection.cursor;
792 selection.start = selection.cursor;
793 selection.end = selection.cursor;
798 // first the toggling area
799 if (cursor.y() < last_sel_cursor.y()
800 || (cursor.y() == last_sel_cursor.y()
801 && cursor.x() < last_sel_cursor.x())) {
802 toggle_end_cursor = last_sel_cursor;
803 toggle_cursor = cursor;
805 toggle_end_cursor = cursor;
806 toggle_cursor = last_sel_cursor;
809 last_sel_cursor = cursor;
811 // and now the whole selection
813 if (selection.cursor.par() == cursor.par())
814 if (selection.cursor.pos() < cursor.pos()) {
815 selection.end = cursor;
816 selection.start = selection.cursor;
818 selection.end = selection.cursor;
819 selection.start = cursor;
821 else if (selection.cursor.y() < cursor.y() ||
822 (selection.cursor.y() == cursor.y()
823 && selection.cursor.x() < cursor.x())) {
824 selection.end = cursor;
825 selection.start = selection.cursor;
828 selection.end = selection.cursor;
829 selection.start = cursor;
832 // a selection with no contents is not a selection
833 if (selection.start.par() == selection.end.par() &&
834 selection.start.pos() == selection.end.pos())
835 selection.set(false);
837 if (inset_owner && (selection.set() || lsel))
838 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
842 string const LyXText::selectionAsString(Buffer const * buffer,
845 if (!selection.set()) return string();
847 // should be const ...
848 Paragraph * startpar(selection.start.par());
849 Paragraph * endpar(selection.end.par());
850 pos_type const startpos(selection.start.pos());
851 pos_type const endpos(selection.end.pos());
853 if (startpar == endpar) {
854 return startpar->asString(buffer, startpos, endpos, label);
859 // First paragraph in selection
860 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
862 // The paragraphs in between (if any)
863 LyXCursor tmpcur(selection.start);
864 tmpcur.par(tmpcur.par()->next());
865 while (tmpcur.par() != endpar) {
866 result += tmpcur.par()->asString(buffer, 0,
867 tmpcur.par()->size(),
869 tmpcur.par(tmpcur.par()->next());
872 // Last paragraph in selection
873 result += endpar->asString(buffer, 0, endpos, label);
879 void LyXText::clearSelection()
881 selection.set(false);
882 selection.mark(false);
883 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
884 // reset this in the bv_owner!
885 if (bv_owner && bv_owner->text)
886 bv_owner->text->xsel_cache.set(false);
890 void LyXText::cursorHome()
892 setCursor(cursor.par(), cursor.row()->pos());
896 void LyXText::cursorEnd()
898 if (cursor.par()->empty())
901 if (!cursor.row()->next()
902 || cursor.row()->next()->par() != cursor.row()->par()) {
903 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
905 if (!cursor.par()->empty() &&
906 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
907 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
908 setCursor(cursor.par(), cursor.row()->lastPos());
910 setCursor(cursor.par(),
911 cursor.row()->lastPos() + 1);
917 void LyXText::cursorTop()
919 while (cursor.par()->previous())
920 cursor.par(cursor.par()->previous());
921 setCursor(cursor.par(), 0);
925 void LyXText::cursorBottom()
927 while (cursor.par()->next())
928 cursor.par(cursor.par()->next());
929 setCursor(cursor.par(), cursor.par()->size());
933 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
935 // If the mask is completely neutral, tell user
936 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
937 // Could only happen with user style
938 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
942 // Try implicit word selection
943 // If there is a change in the language the implicit word selection
945 LyXCursor resetCursor = cursor;
946 bool implicitSelection = (font.language() == ignore_language
947 && font.number() == LyXFont::IGNORE)
948 ? selectWordWhenUnderCursor(WHOLE_WORD_STRICT) : false;
951 setFont(font, toggleall);
953 // Implicit selections are cleared afterwards
954 //and cursor is set to the original position.
955 if (implicitSelection) {
957 cursor = resetCursor;
958 setCursor(cursor.par(), cursor.pos());
959 selection.cursor = cursor;
962 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
966 string LyXText::getStringToIndex()
968 // Try implicit word selection
969 // If there is a change in the language the implicit word selection
971 LyXCursor const reset_cursor = cursor;
972 bool const implicitSelection = selectWordWhenUnderCursor(PREVIOUS_WORD);
975 if (!selection.set())
976 bv()->owner()->message(_("Nothing to index!"));
977 else if (selection.start.par() != selection.end.par())
978 bv()->owner()->message(_("Cannot index more than one paragraph!"));
980 idxstring = selectionAsString(bv()->buffer(), false);
982 // Reset cursors to their original position.
983 cursor = reset_cursor;
984 setCursor(cursor.par(), cursor.pos());
985 selection.cursor = cursor;
987 // Clear the implicit selection.
988 if (implicitSelection)
995 // the DTP switches for paragraphs. LyX will store them in the first
996 // physicla paragraph. When a paragraph is broken, the top settings rest,
997 // the bottom settings are given to the new one. So I can make shure,
998 // they do not duplicate themself and you cannnot make dirty things with
1001 void LyXText::setParagraph(bool line_top, bool line_bottom,
1002 bool pagebreak_top, bool pagebreak_bottom,
1003 VSpace const & space_top,
1004 VSpace const & space_bottom,
1005 Spacing const & spacing,
1007 string const & labelwidthstring,
1010 LyXCursor tmpcursor = cursor;
1011 if (!selection.set()) {
1012 selection.start = cursor;
1013 selection.end = cursor;
1016 // make sure that the depth behind the selection are restored, too
1017 Paragraph * endpar = selection.end.par()->next();
1018 Paragraph * undoendpar = endpar;
1020 if (endpar && endpar->getDepth()) {
1021 while (endpar && endpar->getDepth()) {
1022 endpar = endpar->next();
1023 undoendpar = endpar;
1027 // because of parindents etc.
1028 endpar = endpar->next();
1031 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1034 Paragraph * tmppar = selection.end.par();
1036 while (tmppar != selection.start.par()->previous()) {
1037 setCursor(tmppar, 0);
1038 postPaint(cursor.y() - cursor.row()->baseline());
1039 cursor.par()->params().lineTop(line_top);
1040 cursor.par()->params().lineBottom(line_bottom);
1041 cursor.par()->params().pagebreakTop(pagebreak_top);
1042 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1043 cursor.par()->params().spaceTop(space_top);
1044 cursor.par()->params().spaceBottom(space_bottom);
1045 cursor.par()->params().spacing(spacing);
1046 // does the layout allow the new alignment?
1047 LyXLayout_ptr const & layout = cursor.par()->layout();
1049 if (align == LYX_ALIGN_LAYOUT)
1050 align = layout->align;
1051 if (align & layout->alignpossible) {
1052 if (align == layout->align)
1053 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1055 cursor.par()->params().align(align);
1057 cursor.par()->setLabelWidthString(labelwidthstring);
1058 cursor.par()->params().noindent(noindent);
1059 tmppar = cursor.par()->previous();
1062 redoParagraphs(selection.start, endpar);
1065 setCursor(selection.start.par(), selection.start.pos());
1066 selection.cursor = cursor;
1067 setCursor(selection.end.par(), selection.end.pos());
1069 setCursor(tmpcursor.par(), tmpcursor.pos());
1071 bv()->updateInset(inset_owner);
1075 // set the counter of a paragraph. This includes the labels
1076 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1078 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1079 LyXLayout_ptr const & layout = par->layout();
1081 if (par->previous()) {
1083 par->params().appendix(par->previous()->params().appendix());
1084 if (!par->params().appendix() && par->params().startOfAppendix()) {
1085 par->params().appendix(true);
1086 textclass.counters().reset();
1088 par->enumdepth = par->previous()->enumdepth;
1089 par->itemdepth = par->previous()->itemdepth;
1091 par->params().appendix(par->params().startOfAppendix());
1096 /* Maybe we have to increment the enumeration depth.
1097 * BUT, enumeration in a footnote is considered in isolation from its
1098 * surrounding paragraph so don't increment if this is the
1099 * first line of the footnote
1100 * AND, bibliographies can't have their depth changed ie. they
1101 * are always of depth 0
1104 && par->previous()->getDepth() < par->getDepth()
1105 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1106 && par->enumdepth < 3
1107 && layout->labeltype != LABEL_BIBLIO) {
1111 // Maybe we have to decrement the enumeration depth, see note above
1113 && par->previous()->getDepth() > par->getDepth()
1114 && layout->labeltype != LABEL_BIBLIO) {
1115 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1118 if (!par->params().labelString().empty()) {
1119 par->params().labelString(string());
1122 if (layout->margintype == MARGIN_MANUAL) {
1123 if (par->params().labelWidthString().empty()) {
1124 par->setLabelWidthString(layout->labelstring());
1127 par->setLabelWidthString(string());
1130 // is it a layout that has an automatic label?
1131 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1132 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1136 if (i >= 0 && i <= buf->params.secnumdepth) {
1140 textclass.counters().step(layout->latexname());
1142 // Is there a label? Useful for Chapter layout
1143 if (!par->params().appendix()) {
1144 s << layout->labelstring();
1146 s << layout->labelstring_appendix();
1149 // Use of an integer is here less than elegant. For now.
1150 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1151 if (!par->params().appendix()) {
1152 numbertype = "sectioning";
1154 numbertype = "appendix";
1155 if (par->isRightToLeftPar(buf->params))
1156 langtype = "hebrew";
1161 s << textclass.counters()
1162 .numberLabel(layout->latexname(),
1163 numbertype, langtype, head);
1165 par->params().labelString(STRCONV(s.str()));
1167 // reset enum counters
1168 textclass.counters().reset("enum");
1169 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1170 textclass.counters().reset("enum");
1171 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1173 // Yes I know this is a really, really! bad solution
1175 string enumcounter("enum");
1177 switch (par->enumdepth) {
1186 enumcounter += "iv";
1189 // not a valid enumdepth...
1193 textclass.counters().step(enumcounter);
1195 s << textclass.counters()
1196 .numberLabel(enumcounter, "enumeration");
1197 par->params().labelString(STRCONV(s.str()));
1199 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1200 textclass.counters().step("bibitem");
1201 int number = textclass.counters().value("bibitem");
1202 if (par->bibitem()) {
1203 par->bibitem()->setCounter(number);
1204 par->params().labelString(layout->labelstring());
1206 // In biblio should't be following counters but...
1208 string s = layout->labelstring();
1210 // the caption hack:
1211 if (layout->labeltype == LABEL_SENSITIVE) {
1212 Paragraph * tmppar = par;
1215 while (tmppar && tmppar->inInset()
1216 // the single '=' is intended below
1217 && (in = tmppar->inInset()->owner())) {
1218 if (in->lyxCode() == Inset::FLOAT_CODE ||
1219 in->lyxCode() == Inset::WRAP_CODE) {
1223 tmppar = in->parOwner();
1229 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1231 textclass.counters().step(fl.type());
1233 // Doesn't work... yet.
1234 #warning use boost.format
1235 #if USE_BOOST_FORMAT
1236 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1237 // s << boost::format(_("%1$s %1$d:")
1239 // % buf->counters().value(fl.name());
1242 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1243 o << fl.name() << " #:";
1244 s = STRCONV(o.str());
1247 // par->SetLayout(0);
1248 // s = layout->labelstring;
1249 s = _("Senseless: ");
1252 par->params().labelString(s);
1254 // reset the enumeration counter. They are always reset
1255 // when there is any other layout between
1256 // Just fall-through between the cases so that all
1257 // enum counters deeper than enumdepth is also reset.
1258 switch (par->enumdepth) {
1260 textclass.counters().reset("enumi");
1262 textclass.counters().reset("enumii");
1264 textclass.counters().reset("enumiii");
1266 textclass.counters().reset("enumiv");
1272 // Updates all counters. Paragraphs with changed label string will be rebroken
1273 void LyXText::updateCounters()
1275 RowList::iterator rowit = rows().begin();
1276 Paragraph * par = rowit->par();
1278 // CHECK if this is really needed. (Lgb)
1279 bv()->buffer()->params.getLyXTextClass().counters().reset();
1282 while (rowit->par() != par)
1285 string const oldLabel = par->params().labelString();
1287 // setCounter can potentially change the labelString.
1288 setCounter(bv()->buffer(), par);
1290 string const & newLabel = par->params().labelString();
1292 if (oldLabel.empty() && !newLabel.empty()) {
1293 removeParagraph(&*rowit);
1294 appendParagraph(rowit);
1302 void LyXText::insertInset(Inset * inset)
1304 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1306 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1308 cursor.par()->insertInset(cursor.pos(), inset);
1309 // Just to rebreak and refresh correctly.
1310 // The character will not be inserted a second time
1311 insertChar(Paragraph::META_INSET);
1312 // If we enter a highly editable inset the cursor should be to before
1313 // the inset. This couldn't happen before as Undo was not handled inside
1314 // inset now after the Undo LyX tries to call inset->Edit(...) again
1315 // and cannot do this as the cursor is behind the inset and GetInset
1316 // does not return the inset!
1317 if (isHighlyEditableInset(inset)) {
1324 void LyXText::copyEnvironmentType()
1326 copylayouttype = cursor.par()->layout()->name();
1330 void LyXText::pasteEnvironmentType()
1332 // do nothing if there has been no previous copyEnvironmentType()
1333 if (!copylayouttype.empty())
1334 setLayout(copylayouttype);
1338 void LyXText::cutSelection(bool doclear, bool realcut)
1340 // Stuff what we got on the clipboard. Even if there is no selection.
1342 // There is a problem with having the stuffing here in that the
1343 // larger the selection the slower LyX will get. This can be
1344 // solved by running the line below only when the selection has
1345 // finished. The solution used currently just works, to make it
1346 // faster we need to be more clever and probably also have more
1347 // calls to stuffClipboard. (Lgb)
1348 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1350 // This doesn't make sense, if there is no selection
1351 if (!selection.set())
1354 // OK, we have a selection. This is always between selection.start
1355 // and selection.end
1357 // make sure that the depth behind the selection are restored, too
1358 Paragraph * endpar = selection.end.par()->next();
1359 Paragraph * undoendpar = endpar;
1361 if (endpar && endpar->getDepth()) {
1362 while (endpar && endpar->getDepth()) {
1363 endpar = endpar->next();
1364 undoendpar = endpar;
1366 } else if (endpar) {
1367 endpar = endpar->next(); // because of parindents etc.
1370 setUndo(bv(), Undo::DELETE,
1371 selection.start.par(), undoendpar);
1373 // there are two cases: cut only within one paragraph or
1374 // more than one paragraph
1375 if (selection.start.par() == selection.end.par()) {
1376 // only within one paragraph
1377 endpar = selection.end.par();
1378 int pos = selection.end.pos();
1379 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1380 selection.start.pos(), pos,
1381 bv()->buffer()->params.textclass,
1383 selection.end.pos(pos);
1385 endpar = selection.end.par();
1386 int pos = selection.end.pos();
1387 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1388 selection.start.pos(), pos,
1389 bv()->buffer()->params.textclass,
1392 selection.end.par(endpar);
1393 selection.end.pos(pos);
1394 cursor.pos(selection.end.pos());
1396 endpar = endpar->next();
1398 // sometimes necessary
1400 selection.start.par()->stripLeadingSpaces();
1402 redoParagraphs(selection.start, endpar);
1404 // cutSelection can invalidate the cursor so we need to set
1406 // we prefer the end for when tracking changes
1407 cursor = selection.end;
1409 // need a valid cursor. (Lgb)
1412 setCursor(cursor.par(), cursor.pos());
1413 selection.cursor = cursor;
1418 void LyXText::copySelection()
1420 // stuff the selection onto the X clipboard, from an explicit copy request
1421 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1423 // this doesnt make sense, if there is no selection
1424 if (!selection.set())
1427 // ok we have a selection. This is always between selection.start
1428 // and sel_end cursor
1430 // copy behind a space if there is one
1431 while (selection.start.par()->size() > selection.start.pos()
1432 && selection.start.par()->isLineSeparator(selection.start.pos())
1433 && (selection.start.par() != selection.end.par()
1434 || selection.start.pos() < selection.end.pos()))
1435 selection.start.pos(selection.start.pos() + 1);
1437 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1438 selection.start.pos(), selection.end.pos(),
1439 bv()->buffer()->params.textclass);
1443 void LyXText::pasteSelection()
1445 // this does not make sense, if there is nothing to paste
1446 if (!CutAndPaste::checkPastePossible())
1449 setUndo(bv(), Undo::INSERT,
1450 cursor.par(), cursor.par()->next());
1453 Paragraph * actpar = cursor.par();
1454 int pos = cursor.pos();
1456 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1457 bv()->buffer()->params.textclass);
1459 redoParagraphs(cursor, endpar);
1461 setCursor(cursor.par(), cursor.pos());
1464 selection.cursor = cursor;
1465 setCursor(actpar, pos);
1471 void LyXText::setSelectionRange(lyx::pos_type length)
1476 selection.cursor = cursor;
1483 // simple replacing. The font of the first selected character is used
1484 void LyXText::replaceSelectionWithString(string const & str)
1486 setCursorParUndo(bv());
1489 if (!selection.set()) { // create a dummy selection
1490 selection.end = cursor;
1491 selection.start = cursor;
1494 // Get font setting before we cut
1495 pos_type pos = selection.end.pos();
1496 LyXFont const font = selection.start.par()
1497 ->getFontSettings(bv()->buffer()->params,
1498 selection.start.pos());
1500 // Insert the new string
1501 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1502 selection.end.par()->insertChar(pos, (*cit), font);
1506 // Cut the selection
1507 cutSelection(true, false);
1513 // needed to insert the selection
1514 void LyXText::insertStringAsLines(string const & str)
1516 Paragraph * par = cursor.par();
1517 pos_type pos = cursor.pos();
1518 Paragraph * endpar = cursor.par()->next();
1520 setCursorParUndo(bv());
1522 // only to be sure, should not be neccessary
1525 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1527 redoParagraphs(cursor, endpar);
1528 setCursor(cursor.par(), cursor.pos());
1529 selection.cursor = cursor;
1530 setCursor(par, pos);
1535 // turns double-CR to single CR, others where converted into one
1536 // blank. Then InsertStringAsLines is called
1537 void LyXText::insertStringAsParagraphs(string const & str)
1539 string linestr(str);
1540 bool newline_inserted = false;
1541 for (string::size_type i = 0; i < linestr.length(); ++i) {
1542 if (linestr[i] == '\n') {
1543 if (newline_inserted) {
1544 // we know that \r will be ignored by
1545 // InsertStringA. Of course, it is a dirty
1546 // trick, but it works...
1547 linestr[i - 1] = '\r';
1551 newline_inserted = true;
1553 } else if (IsPrintable(linestr[i])) {
1554 newline_inserted = false;
1557 insertStringAsLines(linestr);
1561 void LyXText::checkParagraph(Paragraph * par,
1564 LyXCursor tmpcursor;
1568 Row * row = getRow(par, pos, y);
1570 // is there a break one row above
1571 if (row->previous() && row->previous()->par() == row->par()) {
1572 z = rowBreakPoint(*row->previous());
1573 if (z >= row->pos()) {
1574 // set the dimensions of the row above
1575 y -= row->previous()->height();
1578 breakAgain(row->previous());
1580 // set the cursor again. Otherwise
1581 // dangling pointers are possible
1582 setCursor(cursor.par(), cursor.pos(),
1583 false, cursor.boundary());
1584 selection.cursor = cursor;
1589 int const tmpheight = row->height();
1590 pos_type const tmplast = row->lastPos();
1593 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1594 postRowPaint(row, y);
1599 // check the special right address boxes
1600 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1607 redoDrawingOfParagraph(tmpcursor);
1610 // set the cursor again. Otherwise dangling pointers are possible
1611 // also set the selection
1613 if (selection.set()) {
1615 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1616 false, selection.cursor.boundary());
1617 selection.cursor = cursor;
1618 setCursorIntern(selection.start.par(),
1619 selection.start.pos(),
1620 false, selection.start.boundary());
1621 selection.start = cursor;
1622 setCursorIntern(selection.end.par(),
1623 selection.end.pos(),
1624 false, selection.end.boundary());
1625 selection.end = cursor;
1626 setCursorIntern(last_sel_cursor.par(),
1627 last_sel_cursor.pos(),
1628 false, last_sel_cursor.boundary());
1629 last_sel_cursor = cursor;
1632 setCursorIntern(cursor.par(), cursor.pos(),
1633 false, cursor.boundary());
1637 // returns false if inset wasn't found
1638 bool LyXText::updateInset(Inset * inset)
1640 // first check the current paragraph
1641 int pos = cursor.par()->getPositionOfInset(inset);
1643 checkParagraph(cursor.par(), pos);
1647 // check every paragraph
1649 Paragraph * par = ownerParagraph();
1651 pos = par->getPositionOfInset(inset);
1653 checkParagraph(par, pos);
1663 bool LyXText::setCursor(Paragraph * par,
1665 bool setfont, bool boundary)
1667 LyXCursor old_cursor = cursor;
1668 setCursorIntern(par, pos, setfont, boundary);
1669 return deleteEmptyParagraphMechanism(old_cursor);
1673 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1674 pos_type pos, bool boundary)
1680 cur.boundary(boundary);
1682 // get the cursor y position in text
1684 Row * row = getRow(par, pos, y);
1685 Row * old_row = row;
1687 // if we are before the first char of this row and are still in the
1688 // same paragraph and there is a previous row then put the cursor on
1689 // the end of the previous row
1690 cur.iy(y + row->baseline());
1692 if (row->previous() && pos &&
1693 row->previous()->par() == row->par() &&
1694 pos < par->size() &&
1695 par->getChar(pos) == Paragraph::META_INSET &&
1696 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1698 row = row->previous();
1703 // y is now the beginning of the cursor row
1704 y += row->baseline();
1705 // y is now the cursor baseline
1708 pos_type last = old_row->lastPrintablePos();
1710 // None of these should happen, but we're scaredy-cats
1711 if (pos > par->size()) {
1712 lyxerr << "dont like 1 please report" << endl;
1715 } else if (pos > last + 1) {
1716 lyxerr << "dont like 2 please report" << endl;
1717 // This shouldn't happen.
1720 } else if (pos < row->pos()) {
1721 lyxerr << "dont like 3 please report" << endl;
1726 // now get the cursors x position
1727 float x = getCursorX(row, pos, last, boundary);
1730 if (old_row != row) {
1731 x = getCursorX(old_row, pos, last, boundary);
1735 //if the cursor is in a visible row, anchor to it
1737 if (topy < y && y < topy + bv()->workHeight())
1742 float LyXText::getCursorX(Row * row,
1743 pos_type pos, pos_type last, bool boundary) const
1745 pos_type cursor_vpos = 0;
1747 float fill_separator;
1749 float fill_label_hfill;
1750 // This call HAS to be here because of the BidiTables!!!
1751 prepareToPrint(row, x, fill_separator, fill_hfill,
1754 if (last < row->pos())
1755 cursor_vpos = row->pos();
1756 else if (pos > last && !boundary)
1757 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1758 ? row->pos() : last + 1;
1759 else if (pos > row->pos() &&
1760 (pos > last || boundary))
1761 /// Place cursor after char at (logical) position pos - 1
1762 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1763 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1765 /// Place cursor before char at (logical) position pos
1766 cursor_vpos = (bidi_level(pos) % 2 == 0)
1767 ? log2vis(pos) : log2vis(pos) + 1;
1769 pos_type body_pos = row->par()->beginningOfBody();
1770 if ((body_pos > 0) &&
1771 ((body_pos-1 > last) ||
1772 !row->par()->isLineSeparator(body_pos - 1)))
1775 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1776 pos_type pos = vis2log(vpos);
1777 if (body_pos > 0 && pos == body_pos - 1) {
1778 x += fill_label_hfill +
1779 font_metrics::width(
1780 row->par()->layout()->labelsep,
1781 getLabelFont(bv()->buffer(),
1783 if (row->par()->isLineSeparator(body_pos - 1))
1785 row->par(), body_pos - 1);
1787 if (row->hfillExpansion(pos)) {
1788 x += singleWidth(row->par(), pos);
1789 if (pos >= body_pos)
1792 x += fill_label_hfill;
1793 } else if (row->par()->isSeparator(pos)) {
1794 x += singleWidth(row->par(), pos);
1795 if (pos >= body_pos)
1796 x += fill_separator;
1798 x += singleWidth(row->par(), pos);
1804 void LyXText::setCursorIntern(Paragraph * par,
1805 pos_type pos, bool setfont, bool boundary)
1807 InsetText * it = static_cast<InsetText *>(par->inInset());
1809 if (it != inset_owner) {
1810 lyxerr[Debug::INSETS] << "InsetText is " << it
1812 << "inset_owner is "
1813 << inset_owner << endl;
1814 #ifdef WITH_WARNINGS
1815 #warning I believe this code is wrong. (Lgb)
1816 #warning Jürgen, have a look at this. (Lgb)
1817 #warning Hmmm, I guess you are right but we
1818 #warning should verify when this is needed
1820 // Jürgen, would you like to have a look?
1821 // I guess we need to move the outer cursor
1822 // and open and lock the inset (bla bla bla)
1823 // stuff I don't know... so can you have a look?
1825 // I moved the lyxerr stuff in here so we can see if
1826 // this is actually really needed and where!
1828 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1833 setCursor(cursor, par, pos, boundary);
1839 void LyXText::setCurrentFont()
1841 pos_type pos = cursor.pos();
1842 if (cursor.boundary() && pos > 0)
1846 if (pos == cursor.par()->size())
1848 else // potentional bug... BUG (Lgb)
1849 if (cursor.par()->isSeparator(pos)) {
1850 if (pos > cursor.row()->pos() &&
1851 bidi_level(pos) % 2 ==
1852 bidi_level(pos - 1) % 2)
1854 else if (pos + 1 < cursor.par()->size())
1860 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1861 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1863 if (cursor.pos() == cursor.par()->size() &&
1864 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1865 !cursor.boundary()) {
1866 Language const * lang =
1867 cursor.par()->getParLanguage(bv()->buffer()->params);
1868 current_font.setLanguage(lang);
1869 current_font.setNumber(LyXFont::OFF);
1870 real_current_font.setLanguage(lang);
1871 real_current_font.setNumber(LyXFont::OFF);
1876 // returns the column near the specified x-coordinate of the row
1877 // x is set to the real beginning of this column
1879 LyXText::getColumnNearX(Row * row, int & x,
1880 bool & boundary) const
1883 float fill_separator;
1885 float fill_label_hfill;
1887 prepareToPrint(row, tmpx, fill_separator,
1888 fill_hfill, fill_label_hfill);
1890 pos_type vc = row->pos();
1891 pos_type last = row->lastPrintablePos();
1894 LyXLayout_ptr const & layout = row->par()->layout();
1896 bool left_side = false;
1898 pos_type body_pos = row->par()->beginningOfBody();
1899 float last_tmpx = tmpx;
1902 (body_pos - 1 > last ||
1903 !row->par()->isLineSeparator(body_pos - 1)))
1906 // check for empty row
1907 if (!row->par()->size()) {
1912 while (vc <= last && tmpx <= x) {
1915 if (body_pos > 0 && c == body_pos-1) {
1916 tmpx += fill_label_hfill +
1917 font_metrics::width(layout->labelsep,
1918 getLabelFont(bv()->buffer(), row->par()));
1919 if (row->par()->isLineSeparator(body_pos - 1))
1920 tmpx -= singleWidth(row->par(), body_pos-1);
1923 if (row->hfillExpansion(c)) {
1924 tmpx += singleWidth(row->par(), c);
1928 tmpx += fill_label_hfill;
1929 } else if (row->par()->isSeparator(c)) {
1930 tmpx += singleWidth(row->par(), c);
1932 tmpx+= fill_separator;
1934 tmpx += singleWidth(row->par(), c);
1939 if ((tmpx + last_tmpx) / 2 > x) {
1944 if (vc > last + 1) // This shouldn't happen.
1948 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
1949 // some speedup if rtl_support=false
1950 && (!row->next() || row->next()->par() != row->par());
1951 bool const rtl = (lastrow)
1952 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
1953 : false; // If lastrow is false, we don't need to compute
1954 // the value of rtl.
1957 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
1958 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
1960 else if (vc == row->pos()) {
1962 if (bidi_level(c) % 2 == 1)
1965 c = vis2log(vc - 1);
1966 bool const rtl = (bidi_level(c) % 2 == 1);
1967 if (left_side == rtl) {
1969 boundary = isBoundary(bv()->buffer(), row->par(), c);
1973 if (row->pos() <= last && c > last
1974 && row->par()->isNewline(last)) {
1975 if (bidi_level(last) % 2 == 0)
1976 tmpx -= singleWidth(row->par(), last);
1978 tmpx += singleWidth(row->par(), last);
1988 void LyXText::setCursorFromCoordinates(int x, int y)
1990 LyXCursor old_cursor = cursor;
1992 setCursorFromCoordinates(cursor, x, y);
1994 deleteEmptyParagraphMechanism(old_cursor);
2001 * return true if the cursor given is at the end of a row,
2002 * and the next row is filled by an inset that spans an entire
2005 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2008 Row const & next = *row.next();
2010 if (next.pos() != cur.pos() || next.par() != cur.par())
2012 if (!cur.par()->isInset(cur.pos()))
2014 Inset const * inset = cur.par()->getInset(cur.pos());
2015 if (inset->needFullRow() || inset->display())
2022 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2025 // Get the row first.
2027 Row * row = getRowNearY(y);
2029 pos_type const column = getColumnNearX(row, x, bound);
2030 cur.par(row->par());
2031 cur.pos(row->pos() + column);
2033 cur.y(y + row->baseline());
2036 if (beforeFullRowInset(*row, cur)) {
2037 pos_type last = row->lastPrintablePos();
2038 float x = getCursorX(row->next(), cur.pos(), last, bound);
2040 cur.iy(y + row->height() + row->next()->baseline());
2041 cur.irow(row->next());
2047 cur.boundary(bound);
2051 void LyXText::cursorLeft(bool internal)
2053 if (cursor.pos() > 0) {
2054 bool boundary = cursor.boundary();
2055 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2056 if (!internal && !boundary &&
2057 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2058 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2059 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2060 Paragraph * par = cursor.par()->previous();
2061 setCursor(par, par->size());
2066 void LyXText::cursorRight(bool internal)
2068 if (!internal && cursor.boundary() &&
2069 !cursor.par()->isNewline(cursor.pos()))
2070 setCursor(cursor.par(), cursor.pos(), true, false);
2071 else if (cursor.pos() < cursor.par()->size()) {
2072 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2074 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2075 setCursor(cursor.par(), cursor.pos(), true, true);
2076 } else if (cursor.par()->next())
2077 setCursor(cursor.par()->next(), 0);
2081 void LyXText::cursorUp(bool selecting)
2084 int x = cursor.x_fix();
2085 int y = cursor.y() - cursor.row()->baseline() - 1;
2086 setCursorFromCoordinates(x, y);
2089 int y1 = cursor.iy() - topy;
2092 Inset * inset_hit = checkInsetHit(x, y1);
2093 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2094 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2098 setCursorFromCoordinates(bv(), cursor.x_fix(),
2099 cursor.y() - cursor.row()->baseline() - 1);
2104 void LyXText::cursorDown(bool selecting)
2107 int x = cursor.x_fix();
2108 int y = cursor.y() - cursor.row()->baseline() +
2109 cursor.row()->height() + 1;
2110 setCursorFromCoordinates(x, y);
2111 if (!selecting && cursor.row() == cursor.irow()) {
2113 int y1 = cursor.iy() - topy;
2116 Inset * inset_hit = checkInsetHit(x, y1);
2117 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2118 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2122 setCursorFromCoordinates(bv(), cursor.x_fix(),
2123 cursor.y() - cursor.row()->baseline()
2124 + cursor.row()->height() + 1);
2129 void LyXText::cursorUpParagraph()
2131 if (cursor.pos() > 0) {
2132 setCursor(cursor.par(), 0);
2134 else if (cursor.par()->previous()) {
2135 setCursor(cursor.par()->previous(), 0);
2140 void LyXText::cursorDownParagraph()
2142 if (cursor.par()->next()) {
2143 setCursor(cursor.par()->next(), 0);
2145 setCursor(cursor.par(), cursor.par()->size());
2149 // fix the cursor `cur' after a characters has been deleted at `where'
2150 // position. Called by deleteEmptyParagraphMechanism
2151 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2152 LyXCursor const & where)
2154 // if cursor is not in the paragraph where the delete occured,
2156 if (cur.par() != where.par())
2159 // if cursor position is after the place where the delete occured,
2161 if (cur.pos() > where.pos())
2162 cur.pos(cur.pos()-1);
2164 // check also if we don't want to set the cursor on a spot behind the
2165 // pagragraph because we erased the last character.
2166 if (cur.pos() > cur.par()->size())
2167 cur.pos(cur.par()->size());
2169 // recompute row et al. for this cursor
2170 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2174 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2176 // Would be wrong to delete anything if we have a selection.
2177 if (selection.set())
2180 // We allow all kinds of "mumbo-jumbo" when freespacing.
2181 if (old_cursor.par()->layout()->free_spacing
2182 || old_cursor.par()->isFreeSpacing()) {
2186 /* Ok I'll put some comments here about what is missing.
2187 I have fixed BackSpace (and thus Delete) to not delete
2188 double-spaces automagically. I have also changed Cut,
2189 Copy and Paste to hopefully do some sensible things.
2190 There are still some small problems that can lead to
2191 double spaces stored in the document file or space at
2192 the beginning of paragraphs. This happens if you have
2193 the cursor betwenn to spaces and then save. Or if you
2194 cut and paste and the selection have a space at the
2195 beginning and then save right after the paste. I am
2196 sure none of these are very hard to fix, but I will
2197 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2198 that I can get some feedback. (Lgb)
2201 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2202 // delete the LineSeparator.
2205 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2206 // delete the LineSeparator.
2209 // If the pos around the old_cursor were spaces, delete one of them.
2210 if (old_cursor.par() != cursor.par()
2211 || old_cursor.pos() != cursor.pos()) {
2212 // Only if the cursor has really moved
2214 if (old_cursor.pos() > 0
2215 && old_cursor.pos() < old_cursor.par()->size()
2216 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2217 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2218 old_cursor.par()->erase(old_cursor.pos() - 1);
2219 redoParagraphs(old_cursor, old_cursor.par()->next());
2221 #ifdef WITH_WARNINGS
2222 #warning This will not work anymore when we have multiple views of the same buffer
2223 // In this case, we will have to correct also the cursors held by
2224 // other bufferviews. It will probably be easier to do that in a more
2225 // automated way in LyXCursor code. (JMarc 26/09/2001)
2227 // correct all cursors held by the LyXText
2228 fixCursorAfterDelete(cursor, old_cursor);
2229 fixCursorAfterDelete(selection.cursor,
2231 fixCursorAfterDelete(selection.start,
2233 fixCursorAfterDelete(selection.end, old_cursor);
2234 fixCursorAfterDelete(last_sel_cursor,
2236 fixCursorAfterDelete(toggle_cursor, old_cursor);
2237 fixCursorAfterDelete(toggle_end_cursor,
2243 // don't delete anything if this is the ONLY paragraph!
2244 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2247 // Do not delete empty paragraphs with keepempty set.
2248 if (old_cursor.par()->layout()->keepempty)
2251 // only do our magic if we changed paragraph
2252 if (old_cursor.par() == cursor.par())
2255 // record if we have deleted a paragraph
2256 // we can't possibly have deleted a paragraph before this point
2257 bool deleted = false;
2259 if ((old_cursor.par()->empty()
2260 || (old_cursor.par()->size() == 1
2261 && old_cursor.par()->isLineSeparator(0)))) {
2262 // ok, we will delete anything
2263 LyXCursor tmpcursor;
2267 if (old_cursor.row()->previous()) {
2268 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2269 - old_cursor.row()->previous()->height());
2271 cursor = old_cursor; // that undo can restore the right cursor position
2272 Paragraph * endpar = old_cursor.par()->next();
2273 if (endpar && endpar->getDepth()) {
2274 while (endpar && endpar->getDepth()) {
2275 endpar = endpar->next();
2278 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2282 removeRow(old_cursor.row());
2283 if (ownerParagraph() == old_cursor.par()) {
2284 ownerParagraph(ownerParagraph()->next());
2287 delete old_cursor.par();
2289 /* Breakagain the next par. Needed because of
2290 * the parindent that can occur or dissappear.
2291 * The next row can change its height, if
2292 * there is another layout before */
2294 if (refresh_row->next()) {
2295 breakAgain(refresh_row->next());
2298 setHeightOfRow(refresh_row);
2301 Row * nextrow = old_cursor.row()->next();
2302 const_cast<LyXText *>(this)->postPaint(
2303 old_cursor.y() - old_cursor.row()->baseline());
2306 cursor = old_cursor; // that undo can restore the right cursor position
2307 Paragraph * endpar = old_cursor.par()->next();
2308 if (endpar && endpar->getDepth()) {
2309 while (endpar && endpar->getDepth()) {
2310 endpar = endpar->next();
2313 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2317 removeRow(old_cursor.row());
2319 if (ownerParagraph() == old_cursor.par()) {
2320 ownerParagraph(ownerParagraph()->next());
2323 delete old_cursor.par();
2325 /* Breakagain the next par. Needed because of
2326 the parindent that can occur or dissappear.
2327 The next row can change its height, if
2328 there is another layout before */
2330 breakAgain(nextrow);
2336 setCursorIntern(cursor.par(), cursor.pos());
2338 if (selection.cursor.par() == old_cursor.par()
2339 && selection.cursor.pos() == old_cursor.pos()) {
2340 // correct selection
2341 selection.cursor = cursor;
2345 if (old_cursor.par()->stripLeadingSpaces()) {
2346 redoParagraphs(old_cursor,
2347 old_cursor.par()->next());
2349 setCursorIntern(cursor.par(), cursor.pos());
2350 selection.cursor = cursor;
2357 Paragraph * LyXText::ownerParagraph() const
2360 return inset_owner->paragraph();
2362 return &*(bv_owner->buffer()->paragraphs.begin());
2366 void LyXText::ownerParagraph(Paragraph * p) const
2369 inset_owner->paragraph(p);
2371 bv_owner->buffer()->paragraphs.set(p);
2376 void LyXText::ownerParagraph(int id, Paragraph * p) const
2378 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2379 if (op && op->inInset()) {
2380 static_cast<InsetText *>(op->inInset())->paragraph(p);
2387 LyXText::refresh_status LyXText::refreshStatus() const
2389 return refresh_status_;
2393 void LyXText::clearPaint()
2395 refresh_status_ = REFRESH_NONE;
2401 void LyXText::postPaint(int start_y)
2403 refresh_status old = refresh_status_;
2405 refresh_status_ = REFRESH_AREA;
2408 if (old != REFRESH_NONE && refresh_y < start_y)
2411 refresh_y = start_y;
2416 // We are an inset's lyxtext. Tell the top-level lyxtext
2417 // it needs to update the row we're in.
2418 LyXText * t = bv()->text;
2419 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2423 // FIXME: we should probably remove this y parameter,
2424 // make refresh_y be 0, and use row->y etc.
2425 void LyXText::postRowPaint(Row * row, int start_y)
2427 if (refresh_status_ != REFRESH_NONE && refresh_y < start_y) {
2428 refresh_status_ = REFRESH_AREA;
2431 refresh_y = start_y;
2434 if (refresh_status_ == REFRESH_AREA)
2437 refresh_status_ = REFRESH_ROW;
2443 // We are an inset's lyxtext. Tell the top-level lyxtext
2444 // it needs to update the row we're in.
2445 LyXText * t = bv()->text;
2446 t->postRowPaint(t->cursor.row(), t->cursor.y() - t->cursor.row()->baseline());
2450 bool LyXText::isInInset() const
2452 // Sub-level has non-null bv owner and
2453 // non-null inset owner.
2454 return inset_owner != 0 && bv_owner != 0;
2458 int defaultRowHeight()
2460 LyXFont const font(LyXFont::ALL_SANE);
2461 return int(font_metrics::maxAscent(font)
2462 + font_metrics::maxDescent(font) * 1.5);