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 * ====================================================== */
14 #pragma implementation "lyxtext.h"
19 #include "paragraph.h"
20 #include "frontends/LyXView.h"
21 #include "undo_funcs.h"
23 #include "bufferparams.h"
25 #include "BufferView.h"
26 #include "CutAndPaste.h"
27 #include "frontends/Painter.h"
28 #include "frontends/font_metrics.h"
32 #include "FloatList.h"
34 #include "ParagraphParameters.h"
37 #include "insets/inseterror.h"
38 #include "insets/insetbib.h"
39 #include "insets/insetspecialchar.h"
40 #include "insets/insettext.h"
41 #include "insets/insetfloat.h"
42 #include "insets/insetwrap.h"
44 #include "support/LAssert.h"
45 #include "support/textutils.h"
46 #include "support/lstrings.h"
48 #include "BoostFormat.h"
58 LyXText::LyXText(BufferView * bv)
59 : height(0), width(0), first_y(0),
60 bv_owner(bv), inset_owner(0), the_locking_inset(0),
61 need_break_row(0), refresh_y(0), refresh_row(0),
62 status_(LyXText::UNCHANGED), firstrow(0), lastrow(0)
66 LyXText::LyXText(InsetText * inset)
67 : height(0), width(0), first_y(0),
68 bv_owner(0), inset_owner(inset), the_locking_inset(0),
69 need_break_row(0), refresh_y(0), refresh_row(0),
70 status_(LyXText::UNCHANGED), firstrow(0), lastrow(0)
74 void LyXText::init(BufferView * bview, bool reinit)
77 // Delete all rows, this does not touch the paragraphs!
78 Row * tmprow = firstrow;
80 tmprow = firstrow->next();
89 copylayouttype.erase();
90 first_y = refresh_y = 0;
91 status_ = LyXText::UNCHANGED;
95 Paragraph * par = ownerParagraph();
96 current_font = getFont(bview->buffer(), par, 0);
99 insertParagraph(bview, par, lastrow);
102 setCursorIntern(bview, firstrow->par(), 0);
103 selection.cursor = cursor;
105 updateCounters(bview);
111 // Delete all rows, this does not touch the paragraphs!
112 Row * tmprow = firstrow;
114 tmprow = firstrow->next();
123 LyXFont const realizeFont(LyXFont const & font,
127 LyXTextClass const & tclass = buf->params.getLyXTextClass();
128 LyXFont tmpfont(font);
129 Paragraph::depth_type par_depth = par->getDepth();
131 // Resolve against environment font information
132 while (par && par_depth && !tmpfont.resolved()) {
133 par = par->outerHook();
135 tmpfont.realize(par->layout()->font);
136 par_depth = par->getDepth();
140 tmpfont.realize(tclass.defaultfont());
148 // Gets the fully instantiated font at a given position in a paragraph
149 // Basically the same routine as Paragraph::getFont() in paragraph.C.
150 // The difference is that this one is used for displaying, and thus we
151 // are allowed to make cosmetic improvements. For instance make footnotes
153 // If position is -1, we get the layout font of the paragraph.
154 // If position is -2, we get the font of the manual label of the paragraph.
155 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
158 lyx::Assert(pos >= 0);
160 LyXLayout_ptr const & layout = par->layout();
162 // We specialize the 95% common case:
163 if (!par->getDepth()) {
164 if (layout->labeltype == LABEL_MANUAL
165 && pos < beginningOfMainBody(buf, par)) {
167 LyXFont f = par->getFontSettings(buf->params, pos);
169 par->inInset()->getDrawFont(f);
170 return f.realize(layout->reslabelfont);
172 LyXFont f = par->getFontSettings(buf->params, pos);
174 par->inInset()->getDrawFont(f);
175 return f.realize(layout->resfont);
179 // The uncommon case need not be optimized as much
183 if (pos < beginningOfMainBody(buf, par)) {
185 layoutfont = layout->labelfont;
188 layoutfont = layout->font;
191 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
192 tmpfont.realize(layoutfont);
195 par->inInset()->getDrawFont(tmpfont);
197 return realizeFont(tmpfont, buf, par);
201 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
203 LyXLayout_ptr const & layout = par->layout();
205 if (!par->getDepth()) {
206 return layout->resfont;
209 return realizeFont(layout->font, buf, par);
213 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
215 LyXLayout_ptr const & layout = par->layout();
217 if (!par->getDepth()) {
218 return layout->reslabelfont;
221 return realizeFont(layout->labelfont, buf, par);
225 void LyXText::setCharFont(BufferView * bv, Paragraph * par,
226 pos_type pos, LyXFont const & fnt,
229 Buffer const * buf = bv->buffer();
230 LyXFont font = getFont(buf, par, pos);
231 font.update(fnt, buf->params.language, toggleall);
232 // Let the insets convert their font
233 if (par->isInset(pos)) {
234 Inset * inset = par->getInset(pos);
235 if (isEditableInset(inset)) {
236 UpdatableInset * uinset =
237 static_cast<UpdatableInset *>(inset);
238 uinset->setFont(bv, fnt, toggleall, true);
242 // Plug thru to version below:
243 setCharFont(buf, par, pos, font);
247 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
248 pos_type pos, LyXFont const & fnt)
252 LyXTextClass const & tclass = buf->params.getLyXTextClass();
253 LyXLayout_ptr const & layout = par->layout();
255 // Get concrete layout font to reduce against
258 if (pos < beginningOfMainBody(buf, par))
259 layoutfont = layout->labelfont;
261 layoutfont = layout->font;
263 // Realize against environment font information
264 if (par->getDepth()) {
265 Paragraph * tp = par;
266 while (!layoutfont.resolved() && tp && tp->getDepth()) {
267 tp = tp->outerHook();
269 layoutfont.realize(tp->layout()->font);
273 layoutfont.realize(tclass.defaultfont());
275 // Now, reduce font against full layout font
276 font.reduce(layoutfont);
278 par->setFont(pos, font);
282 // inserts a new row behind the specified row, increments
283 // the touched counters
284 void LyXText::insertRow(Row * row, Paragraph * par,
287 Row * tmprow = new Row;
290 tmprow->next(firstrow);
293 tmprow->previous(row);
294 tmprow->next(row->next());
299 tmprow->next()->previous(tmprow);
301 if (tmprow->previous())
302 tmprow->previous()->next(tmprow);
313 // removes the row and reset the touched counters
314 void LyXText::removeRow(Row * row) const
316 Row * row_prev = row->previous();
318 row->next()->previous(row_prev);
320 firstrow = row->next();
321 // lyx::Assert(firstrow);
323 row_prev->next(row->next());
325 if (row == lastrow) {
326 lyx::Assert(!row->next());
329 if (refresh_row == row) {
330 refresh_row = row_prev ? row_prev : row->next();
331 // what about refresh_y, refresh_height
334 height -= row->height(); // the text becomes smaller
340 // remove all following rows of the paragraph of the specified row.
341 void LyXText::removeParagraph(Row * row) const
343 Paragraph * tmppar = row->par();
347 while (row && row->par() == tmppar) {
348 tmprow = row->next();
355 // insert the specified paragraph behind the specified row
356 void LyXText::insertParagraph(BufferView * bview, Paragraph * par,
359 // insert a new row, starting at position 0
360 insertRow(row, par, 0);
362 // and now append the whole paragraph behind the new row
365 appendParagraph(bview, firstrow);
367 row->next()->height(0);
368 appendParagraph(bview, row->next());
373 Inset * LyXText::getInset() const
376 if (cursor.pos() == 0 && cursor.par()->bibkey) {
377 inset = cursor.par()->bibkey;
378 } else if (cursor.pos() < cursor.par()->size()
379 && cursor.par()->isInset(cursor.pos())) {
380 inset = cursor.par()->getInset(cursor.pos());
386 void LyXText::toggleInset(BufferView * bview)
388 Inset * inset = getInset();
389 // is there an editable inset at cursor position?
390 if (!isEditableInset(inset)) {
391 // No, try to see if we are inside a collapsable inset
392 if (inset_owner && inset_owner->owner()
393 && inset_owner->owner()->isOpen()) {
394 bview->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
395 inset_owner->owner()->close(bview);
396 bview->getLyXText()->cursorRight(bview);
400 //bview->owner()->message(inset->editMessage());
402 // do we want to keep this?? (JMarc)
403 if (!isHighlyEditableInset(inset))
404 setCursorParUndo(bview);
406 if (inset->isOpen()) {
412 inset->open(bview, !inset->isOpen());
417 /* used in setlayout */
418 // Asger is not sure we want to do this...
419 void LyXText::makeFontEntriesLayoutSpecific(Buffer const * buf,
422 LyXLayout_ptr const & layout = par->layout();
425 for (pos_type pos = 0; pos < par->size(); ++pos) {
426 if (pos < beginningOfMainBody(buf, par))
427 layoutfont = layout->labelfont;
429 layoutfont = layout->font;
431 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
432 tmpfont.reduce(layoutfont);
433 par->setFont(pos, tmpfont);
438 Paragraph * LyXText::setLayout(BufferView * bview,
439 LyXCursor & cur, LyXCursor & sstart_cur,
440 LyXCursor & send_cur,
441 string const & layout)
443 Paragraph * endpar = send_cur.par()->next();
444 Paragraph * undoendpar = endpar;
446 if (endpar && endpar->getDepth()) {
447 while (endpar && endpar->getDepth()) {
448 endpar = endpar->next();
452 endpar = endpar->next(); // because of parindents etc.
455 setUndo(bview, Undo::EDIT, sstart_cur.par(), undoendpar);
457 // ok we have a selection. This is always between sstart_cur
458 // and sel_end cursor
460 Paragraph * par = sstart_cur.par();
461 Paragraph * epar = send_cur.par()->next();
463 LyXLayout_ptr const & lyxlayout =
464 bview->buffer()->params.getLyXTextClass()[layout];
467 par->applyLayout(lyxlayout);
468 makeFontEntriesLayoutSpecific(bview->buffer(), par);
469 Paragraph * fppar = par;
470 fppar->params().spaceTop(lyxlayout->fill_top ?
471 VSpace(VSpace::VFILL)
472 : VSpace(VSpace::NONE));
473 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
474 VSpace(VSpace::VFILL)
475 : VSpace(VSpace::NONE));
476 if (lyxlayout->margintype == MARGIN_MANUAL)
477 par->setLabelWidthString(lyxlayout->labelstring());
478 if (lyxlayout->labeltype != LABEL_BIBLIO
480 delete fppar->bibkey;
485 } while (par != epar);
491 // set layout over selection and make a total rebreak of those paragraphs
492 void LyXText::setLayout(BufferView * bview, string const & layout)
494 LyXCursor tmpcursor = cursor; /* store the current cursor */
496 // if there is no selection just set the layout
497 // of the current paragraph */
498 if (!selection.set()) {
499 selection.start = cursor; // dummy selection
500 selection.end = cursor;
502 Paragraph * endpar = setLayout(bview, cursor, selection.start,
503 selection.end, layout);
504 redoParagraphs(bview, selection.start, endpar);
506 // we have to reset the selection, because the
507 // geometry could have changed
508 setCursor(bview, selection.start.par(),
509 selection.start.pos(), false);
510 selection.cursor = cursor;
511 setCursor(bview, selection.end.par(), selection.end.pos(), false);
512 updateCounters(bview);
515 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true);
519 // increment depth over selection and
520 // make a total rebreak of those paragraphs
521 void LyXText::incDepth(BufferView * bview)
523 // If there is no selection, just use the current paragraph
524 if (!selection.set()) {
525 selection.start = cursor; // dummy selection
526 selection.end = cursor;
529 // We end at the next paragraph with depth 0
530 Paragraph * endpar = selection.end.par()->next();
532 Paragraph * undoendpar = endpar;
534 if (endpar && endpar->getDepth()) {
535 while (endpar && endpar->getDepth()) {
536 endpar = endpar->next();
540 endpar = endpar->next(); // because of parindents etc.
543 setUndo(bview, Undo::EDIT,
544 selection.start.par(), undoendpar);
546 LyXCursor tmpcursor = cursor; // store the current cursor
548 // ok we have a selection. This is always between sel_start_cursor
549 // and sel_end cursor
550 cursor = selection.start;
553 // NOTE: you can't change the depth of a bibliography entry
554 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
555 Paragraph * prev = cursor.par()->previous();
558 if (cursor.par()->getDepth()
559 < prev->getMaxDepthAfter()) {
560 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
564 if (cursor.par() == selection.end.par())
566 cursor.par(cursor.par()->next());
569 redoParagraphs(bview, selection.start, endpar);
571 // we have to reset the selection, because the
572 // geometry could have changed
573 setCursor(bview, selection.start.par(), selection.start.pos());
574 selection.cursor = cursor;
575 setCursor(bview, selection.end.par(), selection.end.pos());
576 updateCounters(bview);
579 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
583 // decrement depth over selection and
584 // make a total rebreak of those paragraphs
585 void LyXText::decDepth(BufferView * bview)
587 // if there is no selection just set the layout
588 // of the current paragraph
589 if (!selection.set()) {
590 selection.start = cursor; // dummy selection
591 selection.end = cursor;
593 Paragraph * endpar = selection.end.par()->next();
594 Paragraph * undoendpar = endpar;
596 if (endpar && endpar->getDepth()) {
597 while (endpar && endpar->getDepth()) {
598 endpar = endpar->next();
602 endpar = endpar->next(); // because of parindents etc.
605 setUndo(bview, Undo::EDIT,
606 selection.start.par(), undoendpar);
608 LyXCursor tmpcursor = cursor; // store the current cursor
610 // ok we have a selection. This is always between sel_start_cursor
611 // and sel_end cursor
612 cursor = selection.start;
615 if (cursor.par()->params().depth()) {
616 cursor.par()->params()
617 .depth(cursor.par()->params().depth() - 1);
619 if (cursor.par() == selection.end.par()) {
622 cursor.par(cursor.par()->next());
625 redoParagraphs(bview, selection.start, endpar);
627 // we have to reset the selection, because the
628 // geometry could have changed
629 setCursor(bview, selection.start.par(),
630 selection.start.pos());
631 selection.cursor = cursor;
632 setCursor(bview, selection.end.par(), selection.end.pos());
633 updateCounters(bview);
636 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
640 // set font over selection and make a total rebreak of those paragraphs
641 void LyXText::setFont(BufferView * bview, LyXFont const & font, bool toggleall)
643 // if there is no selection just set the current_font
644 if (!selection.set()) {
645 // Determine basis font
647 if (cursor.pos() < beginningOfMainBody(bview->buffer(),
649 layoutfont = getLabelFont(bview->buffer(),
652 layoutfont = getLayoutFont(bview->buffer(),
655 // Update current font
656 real_current_font.update(font,
657 bview->buffer()->params.language,
660 // Reduce to implicit settings
661 current_font = real_current_font;
662 current_font.reduce(layoutfont);
663 // And resolve it completely
664 real_current_font.realize(layoutfont);
669 LyXCursor tmpcursor = cursor; // store the current cursor
671 // ok we have a selection. This is always between sel_start_cursor
672 // and sel_end cursor
674 setUndo(bview, Undo::EDIT,
675 selection.start.par(), selection.end.par()->next());
677 cursor = selection.start;
678 while (cursor.par() != selection.end.par() ||
679 cursor.pos() < selection.end.pos())
681 if (cursor.pos() < cursor.par()->size()) {
682 // an open footnote should behave like a closed one
683 setCharFont(bview, cursor.par(), cursor.pos(),
685 cursor.pos(cursor.pos() + 1);
688 cursor.par(cursor.par()->next());
693 redoParagraphs(bview, selection.start, selection.end.par()->next());
695 // we have to reset the selection, because the
696 // geometry could have changed, but we keep
697 // it for user convenience
698 setCursor(bview, selection.start.par(), selection.start.pos());
699 selection.cursor = cursor;
700 setCursor(bview, selection.end.par(), selection.end.pos());
702 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true,
703 tmpcursor.boundary());
707 void LyXText::redoHeightOfParagraph(BufferView * bview, LyXCursor const & cur)
709 Row * tmprow = cur.row();
710 int y = cur.y() - tmprow->baseline();
712 setHeightOfRow(bview, tmprow);
714 while (tmprow->previous()
715 && tmprow->previous()->par() == tmprow->par()) {
716 tmprow = tmprow->previous();
717 y -= tmprow->height();
718 setHeightOfRow(bview, tmprow);
721 // we can set the refreshing parameters now
722 status(bview, LyXText::NEED_MORE_REFRESH);
724 refresh_row = tmprow;
725 setCursor(bview, cur.par(), cur.pos(), false, cursor.boundary());
729 void LyXText::redoDrawingOfParagraph(BufferView * bview, LyXCursor const & cur)
731 Row * tmprow = cur.row();
733 int y = cur.y() - tmprow->baseline();
734 setHeightOfRow(bview, tmprow);
736 while (tmprow->previous()
737 && tmprow->previous()->par() == tmprow->par()) {
738 tmprow = tmprow->previous();
739 y -= tmprow->height();
742 // we can set the refreshing parameters now
743 if (status_ == LyXText::UNCHANGED || y < refresh_y) {
745 refresh_row = tmprow;
747 status(bview, LyXText::NEED_MORE_REFRESH);
748 setCursor(bview, cur.par(), cur.pos());
752 // deletes and inserts again all paragaphs between the cursor
753 // and the specified par
754 // This function is needed after SetLayout and SetFont etc.
755 void LyXText::redoParagraphs(BufferView * bview, LyXCursor const & cur,
756 Paragraph const * endpar) const
759 Paragraph * tmppar = 0;
760 Paragraph * first_phys_par = 0;
762 Row * tmprow = cur.row();
764 int y = cur.y() - tmprow->baseline();
766 if (!tmprow->previous()) {
767 // a trick/hack for UNDO
768 // This is needed because in an UNDO/REDO we could have changed
769 // the ownerParagrah() so the paragraph inside the row is NOT
770 // my really first par anymore. Got it Lars ;) (Jug 20011206)
771 first_phys_par = ownerParagraph();
773 first_phys_par = tmprow->par();
774 while (tmprow->previous()
775 && tmprow->previous()->par() == first_phys_par)
777 tmprow = tmprow->previous();
778 y -= tmprow->height();
782 // we can set the refreshing parameters now
783 status(bview, LyXText::NEED_MORE_REFRESH);
785 refresh_row = tmprow->previous(); /* the real refresh row will
786 be deleted, so I store
790 tmppar = tmprow->next()->par();
793 while (tmprow->next() && tmppar != endpar) {
794 removeRow(tmprow->next());
795 if (tmprow->next()) {
796 tmppar = tmprow->next()->par();
802 // remove the first one
803 tmprow2 = tmprow; /* this is because tmprow->previous()
805 tmprow = tmprow->previous();
808 tmppar = first_phys_par;
812 insertParagraph(bview, tmppar, tmprow);
816 while (tmprow->next()
817 && tmprow->next()->par() == tmppar) {
818 tmprow = tmprow->next();
820 tmppar = tmppar->next();
822 } while (tmppar && tmppar != endpar);
824 // this is because of layout changes
826 refresh_y -= refresh_row->height();
827 setHeightOfRow(bview, refresh_row);
829 refresh_row = firstrow;
831 setHeightOfRow(bview, refresh_row);
834 if (tmprow && tmprow->next())
835 setHeightOfRow(bview, tmprow->next());
836 updateCounters(bview);
840 void LyXText::fullRebreak(BufferView * bview)
846 if (need_break_row) {
847 breakAgain(bview, need_break_row);
854 // important for the screen
857 // the cursor set functions have a special mechanism. When they
858 // realize, that you left an empty paragraph, they will delete it.
859 // They also delete the corresponding row
861 // need the selection cursor:
862 void LyXText::setSelection(BufferView * bview)
864 bool const lsel = selection.set();
866 if (!selection.set()) {
867 last_sel_cursor = selection.cursor;
868 selection.start = selection.cursor;
869 selection.end = selection.cursor;
874 // first the toggling area
875 if (cursor.y() < last_sel_cursor.y()
876 || (cursor.y() == last_sel_cursor.y()
877 && cursor.x() < last_sel_cursor.x())) {
878 toggle_end_cursor = last_sel_cursor;
879 toggle_cursor = cursor;
881 toggle_end_cursor = cursor;
882 toggle_cursor = last_sel_cursor;
885 last_sel_cursor = cursor;
887 // and now the whole selection
889 if (selection.cursor.par() == cursor.par())
890 if (selection.cursor.pos() < cursor.pos()) {
891 selection.end = cursor;
892 selection.start = selection.cursor;
894 selection.end = selection.cursor;
895 selection.start = cursor;
897 else if (selection.cursor.y() < cursor.y() ||
898 (selection.cursor.y() == cursor.y()
899 && selection.cursor.x() < cursor.x())) {
900 selection.end = cursor;
901 selection.start = selection.cursor;
904 selection.end = selection.cursor;
905 selection.start = cursor;
908 // a selection with no contents is not a selection
909 if (selection.start.par() == selection.end.par() &&
910 selection.start.pos() == selection.end.pos())
911 selection.set(false);
913 if (inset_owner && (selection.set() || lsel))
914 inset_owner->setUpdateStatus(bview, InsetText::SELECTION);
918 string const LyXText::selectionAsString(Buffer const * buffer,
921 if (!selection.set()) return string();
923 // should be const ...
924 Paragraph * startpar(selection.start.par());
925 Paragraph * endpar(selection.end.par());
926 pos_type const startpos(selection.start.pos());
927 pos_type const endpos(selection.end.pos());
929 if (startpar == endpar) {
930 return startpar->asString(buffer, startpos, endpos, label);
935 // First paragraph in selection
936 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
938 // The paragraphs in between (if any)
939 LyXCursor tmpcur(selection.start);
940 tmpcur.par(tmpcur.par()->next());
941 while (tmpcur.par() != endpar) {
942 result += tmpcur.par()->asString(buffer, 0,
943 tmpcur.par()->size(),
945 tmpcur.par(tmpcur.par()->next());
948 // Last paragraph in selection
949 result += endpar->asString(buffer, 0, endpos, label);
955 void LyXText::clearSelection() const
957 selection.set(false);
958 selection.mark(false);
959 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
960 // reset this in the bv_owner!
961 if (bv_owner && bv_owner->text)
962 bv_owner->text->xsel_cache.set(false);
966 void LyXText::cursorHome(BufferView * bview) const
968 setCursor(bview, cursor.par(), cursor.row()->pos());
972 void LyXText::cursorEnd(BufferView * bview) const
974 if (!cursor.row()->next()
975 || cursor.row()->next()->par() != cursor.row()->par()) {
976 setCursor(bview, cursor.par(), rowLast(cursor.row()) + 1);
978 if (!cursor.par()->empty() &&
979 (cursor.par()->getChar(rowLast(cursor.row())) == ' '
980 || cursor.par()->isNewline(rowLast(cursor.row())))) {
981 setCursor(bview, cursor.par(), rowLast(cursor.row()));
983 setCursor(bview,cursor.par(),
984 rowLast(cursor.row()) + 1);
990 void LyXText::cursorTop(BufferView * bview) const
992 while (cursor.par()->previous())
993 cursor.par(cursor.par()->previous());
994 setCursor(bview, cursor.par(), 0);
998 void LyXText::cursorBottom(BufferView * bview) const
1000 while (cursor.par()->next())
1001 cursor.par(cursor.par()->next());
1002 setCursor(bview, cursor.par(), cursor.par()->size());
1006 void LyXText::toggleFree(BufferView * bview,
1007 LyXFont const & font, bool toggleall)
1009 // If the mask is completely neutral, tell user
1010 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
1011 // Could only happen with user style
1012 bview->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
1016 // Try implicit word selection
1017 // If there is a change in the language the implicit word selection
1019 LyXCursor resetCursor = cursor;
1020 bool implicitSelection = (font.language() == ignore_language
1021 && font.number() == LyXFont::IGNORE)
1022 ? selectWordWhenUnderCursor(bview, WHOLE_WORD_STRICT) : false;
1025 setFont(bview, font, toggleall);
1027 // Implicit selections are cleared afterwards
1028 //and cursor is set to the original position.
1029 if (implicitSelection) {
1031 cursor = resetCursor;
1032 setCursor(bview, cursor.par(), cursor.pos());
1033 selection.cursor = cursor;
1036 inset_owner->setUpdateStatus(bview, InsetText::CURSOR_PAR);
1040 string LyXText::getStringToIndex(BufferView * bview)
1042 // Try implicit word selection
1043 // If there is a change in the language the implicit word selection
1045 LyXCursor const reset_cursor = cursor;
1046 bool const implicitSelection = selectWordWhenUnderCursor(bview, PREVIOUS_WORD);
1049 if (!selection.set())
1050 bview->owner()->message(_("Nothing to index!"));
1051 else if (selection.start.par() != selection.end.par())
1052 bview->owner()->message(_("Cannot index more than one paragraph!"));
1054 idxstring = selectionAsString(bview->buffer(), false);
1056 // Reset cursors to their original position.
1057 cursor = reset_cursor;
1058 setCursor(bview, cursor.par(), cursor.pos());
1059 selection.cursor = cursor;
1061 // Clear the implicit selection.
1062 if (implicitSelection)
1069 pos_type LyXText::beginningOfMainBody(Buffer const * /*buf*/,
1070 Paragraph const * par) const
1072 if (par->layout()->labeltype != LABEL_MANUAL)
1075 return par->beginningOfMainBody();
1079 // the DTP switches for paragraphs. LyX will store them in the first
1080 // physicla paragraph. When a paragraph is broken, the top settings rest,
1081 // the bottom settings are given to the new one. So I can make shure,
1082 // they do not duplicate themself and you cannnot make dirty things with
1085 void LyXText::setParagraph(BufferView * bview,
1086 bool line_top, bool line_bottom,
1087 bool pagebreak_top, bool pagebreak_bottom,
1088 VSpace const & space_top,
1089 VSpace const & space_bottom,
1090 Spacing const & spacing,
1092 string labelwidthstring,
1095 LyXCursor tmpcursor = cursor;
1096 if (!selection.set()) {
1097 selection.start = cursor;
1098 selection.end = cursor;
1101 // make sure that the depth behind the selection are restored, too
1102 Paragraph * endpar = selection.end.par()->next();
1103 Paragraph * undoendpar = endpar;
1105 if (endpar && endpar->getDepth()) {
1106 while (endpar && endpar->getDepth()) {
1107 endpar = endpar->next();
1108 undoendpar = endpar;
1112 // because of parindents etc.
1113 endpar = endpar->next();
1116 setUndo(bview, Undo::EDIT, selection.start.par(), undoendpar);
1119 Paragraph * tmppar = selection.end.par();
1121 while (tmppar != selection.start.par()->previous()) {
1122 setCursor(bview, tmppar, 0);
1123 status(bview, LyXText::NEED_MORE_REFRESH);
1124 refresh_row = cursor.row();
1125 refresh_y = cursor.y() - cursor.row()->baseline();
1126 cursor.par()->params().lineTop(line_top);
1127 cursor.par()->params().lineBottom(line_bottom);
1128 cursor.par()->params().pagebreakTop(pagebreak_top);
1129 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1130 cursor.par()->params().spaceTop(space_top);
1131 cursor.par()->params().spaceBottom(space_bottom);
1132 cursor.par()->params().spacing(spacing);
1133 // does the layout allow the new alignment?
1134 LyXLayout_ptr const & layout = cursor.par()->layout();
1136 if (align == LYX_ALIGN_LAYOUT)
1137 align = layout->align;
1138 if (align & layout->alignpossible) {
1139 if (align == layout->align)
1140 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1142 cursor.par()->params().align(align);
1144 cursor.par()->setLabelWidthString(labelwidthstring);
1145 cursor.par()->params().noindent(noindent);
1146 tmppar = cursor.par()->previous();
1149 redoParagraphs(bview, selection.start, endpar);
1152 setCursor(bview, selection.start.par(), selection.start.pos());
1153 selection.cursor = cursor;
1154 setCursor(bview, selection.end.par(), selection.end.pos());
1155 setSelection(bview);
1156 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
1158 bview->updateInset(inset_owner, true);
1162 // set the counter of a paragraph. This includes the labels
1163 void LyXText::setCounter(Buffer const * buf, Paragraph * par) const
1165 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1166 LyXLayout_ptr const & layout = par->layout();
1168 if (par->previous()) {
1170 par->params().appendix(par->previous()->params().appendix());
1171 if (!par->params().appendix() && par->params().startOfAppendix()) {
1172 par->params().appendix(true);
1173 textclass.counters().reset();
1175 par->enumdepth = par->previous()->enumdepth;
1176 par->itemdepth = par->previous()->itemdepth;
1178 par->params().appendix(par->params().startOfAppendix());
1183 /* Maybe we have to increment the enumeration depth.
1184 * BUT, enumeration in a footnote is considered in isolation from its
1185 * surrounding paragraph so don't increment if this is the
1186 * first line of the footnote
1187 * AND, bibliographies can't have their depth changed ie. they
1188 * are always of depth 0
1191 && par->previous()->getDepth() < par->getDepth()
1192 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1193 && par->enumdepth < 3
1194 && layout->labeltype != LABEL_BIBLIO) {
1198 // Maybe we have to decrement the enumeration depth, see note above
1200 && par->previous()->getDepth() > par->getDepth()
1201 && layout->labeltype != LABEL_BIBLIO) {
1202 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1205 if (!par->params().labelString().empty()) {
1206 par->params().labelString(string());
1209 if (layout->margintype == MARGIN_MANUAL) {
1210 if (par->params().labelWidthString().empty()) {
1211 par->setLabelWidthString(layout->labelstring());
1214 par->setLabelWidthString(string());
1217 // is it a layout that has an automatic label?
1218 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1219 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1223 if (i >= 0 && i <= buf->params.secnumdepth) {
1227 textclass.counters().step(layout->latexname());
1229 // Is there a label? Useful for Chapter layout
1230 if (!par->params().appendix()) {
1231 s << layout->labelstring();
1233 s << layout->labelstring_appendix();
1236 // Use of an integer is here less than elegant. For now.
1237 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1238 if (!par->params().appendix()) {
1239 numbertype = "sectioning";
1241 numbertype = "appendix";
1242 if (par->isRightToLeftPar(buf->params))
1243 langtype = "hebrew";
1248 s << textclass.counters()
1249 .numberLabel(layout->latexname(),
1250 numbertype, langtype, head);
1252 par->params().labelString(STRCONV(s.str()));
1254 // reset enum counters
1255 textclass.counters().reset("enum");
1256 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1257 textclass.counters().reset("enum");
1258 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1260 // Yes I know this is a really, really! bad solution
1262 string enumcounter("enum");
1264 switch (par->enumdepth) {
1273 enumcounter += "iv";
1276 // not a valid enumdepth...
1280 textclass.counters().step(enumcounter);
1282 s << textclass.counters()
1283 .numberLabel(enumcounter, "enumeration");
1284 par->params().labelString(STRCONV(s.str()));
1286 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1287 textclass.counters().step("bibitem");
1288 int number = textclass.counters().value("bibitem");
1290 InsetCommandParams p("bibitem");
1291 par->bibkey = new InsetBibKey(p);
1293 par->bibkey->setCounter(number);
1294 par->params().labelString(layout->labelstring());
1296 // In biblio should't be following counters but...
1298 string s = layout->labelstring();
1300 // the caption hack:
1301 if (layout->labeltype == LABEL_SENSITIVE) {
1302 Paragraph * tmppar = par;
1305 while (tmppar && tmppar->inInset()
1306 // the single '=' is intended below
1307 && (in = tmppar->inInset()->owner())) {
1308 if (in->lyxCode() == Inset::FLOAT_CODE ||
1309 in->lyxCode() == Inset::WRAP_CODE) {
1313 tmppar = in->parOwner();
1319 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1321 textclass.counters().step(fl.type());
1323 // Doesn't work... yet.
1324 #warning use boost.format
1325 #if USE_BOOST_FORMAT
1326 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1327 // s << boost::format(_("%1$s %1$d:")
1329 // % buf->counters().value(fl.name());
1332 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1333 o << fl.name() << " #:";
1334 s = STRCONV(o.str());
1337 // par->SetLayout(0);
1338 // s = layout->labelstring;
1339 s = _("Senseless: ");
1342 par->params().labelString(s);
1344 // reset the enumeration counter. They are always reset
1345 // when there is any other layout between
1346 // Just fall-through between the cases so that all
1347 // enum counters deeper than enumdepth is also reset.
1348 switch (par->enumdepth) {
1350 textclass.counters().reset("enumi");
1352 textclass.counters().reset("enumii");
1354 textclass.counters().reset("enumiii");
1356 textclass.counters().reset("enumiv");
1362 // Updates all counters. Paragraphs with changed label string will be rebroken
1363 void LyXText::updateCounters(BufferView * bview) const
1365 Row * row = firstrow;
1366 Paragraph * par = row->par();
1368 // CHECK if this is really needed. (Lgb)
1369 bview->buffer()->params.getLyXTextClass().counters().reset();
1372 while (row->par() != par)
1375 string const oldLabel = par->params().labelString();
1377 // setCounter can potentially change the labelString.
1378 setCounter(bview->buffer(), par);
1380 string const & newLabel = par->params().labelString();
1382 if (oldLabel.empty() && !newLabel.empty()) {
1383 removeParagraph(row);
1384 appendParagraph(bview, row);
1392 void LyXText::insertInset(BufferView * bview, Inset * inset)
1394 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1396 setUndo(bview, Undo::FINISH, cursor.par(), cursor.par()->next());
1398 cursor.par()->insertInset(cursor.pos(), inset);
1399 // Just to rebreak and refresh correctly.
1400 // The character will not be inserted a second time
1401 insertChar(bview, Paragraph::META_INSET);
1402 // If we enter a highly editable inset the cursor should be to before
1403 // the inset. This couldn't happen before as Undo was not handled inside
1404 // inset now after the Undo LyX tries to call inset->Edit(...) again
1405 // and cannot do this as the cursor is behind the inset and GetInset
1406 // does not return the inset!
1407 if (isHighlyEditableInset(inset)) {
1408 cursorLeft(bview, true);
1414 void LyXText::copyEnvironmentType()
1416 copylayouttype = cursor.par()->layout()->name();
1420 void LyXText::pasteEnvironmentType(BufferView * bview)
1422 // do nothing if there has been no previous copyEnvironmentType()
1423 if (!copylayouttype.empty())
1424 setLayout(bview, copylayouttype);
1428 void LyXText::cutSelection(BufferView * bview, bool doclear, bool realcut)
1430 // Stuff what we got on the clipboard. Even if there is no selection.
1432 // There is a problem with having the stuffing here in that the
1433 // larger the selection the slower LyX will get. This can be
1434 // solved by running the line below only when the selection has
1435 // finished. The solution used currently just works, to make it
1436 // faster we need to be more clever and probably also have more
1437 // calls to stuffClipboard. (Lgb)
1438 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1440 // This doesn't make sense, if there is no selection
1441 if (!selection.set())
1444 // OK, we have a selection. This is always between selection.start
1445 // and selection.end
1447 // make sure that the depth behind the selection are restored, too
1448 Paragraph * endpar = selection.end.par()->next();
1449 Paragraph * undoendpar = endpar;
1451 if (endpar && endpar->getDepth()) {
1452 while (endpar && endpar->getDepth()) {
1453 endpar = endpar->next();
1454 undoendpar = endpar;
1456 } else if (endpar) {
1457 endpar = endpar->next(); // because of parindents etc.
1460 setUndo(bview, Undo::DELETE,
1461 selection.start.par(), undoendpar);
1463 // there are two cases: cut only within one paragraph or
1464 // more than one paragraph
1465 if (selection.start.par() == selection.end.par()) {
1466 // only within one paragraph
1467 endpar = selection.end.par();
1468 int pos = selection.end.pos();
1469 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1470 selection.start.pos(), pos,
1471 bview->buffer()->params.textclass,
1473 selection.end.pos(pos);
1475 endpar = selection.end.par();
1476 int pos = selection.end.pos();
1477 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1478 selection.start.pos(), pos,
1479 bview->buffer()->params.textclass,
1482 selection.end.par(endpar);
1483 selection.end.pos(pos);
1484 cursor.pos(selection.end.pos());
1486 endpar = endpar->next();
1488 // sometimes necessary
1490 selection.start.par()->stripLeadingSpaces();
1492 redoParagraphs(bview, selection.start, endpar);
1494 // cutSelection can invalidate the cursor so we need to set
1496 // we prefer the end for when tracking changes
1497 cursor = selection.end;
1499 // need a valid cursor. (Lgb)
1502 setCursor(bview, cursor.par(), cursor.pos());
1503 selection.cursor = cursor;
1504 updateCounters(bview);
1508 void LyXText::copySelection(BufferView * bview)
1510 // stuff the selection onto the X clipboard, from an explicit copy request
1511 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1513 // this doesnt make sense, if there is no selection
1514 if (!selection.set())
1517 // ok we have a selection. This is always between selection.start
1518 // and sel_end cursor
1520 // copy behind a space if there is one
1521 while (selection.start.par()->size() > selection.start.pos()
1522 && selection.start.par()->isLineSeparator(selection.start.pos())
1523 && (selection.start.par() != selection.end.par()
1524 || selection.start.pos() < selection.end.pos()))
1525 selection.start.pos(selection.start.pos() + 1);
1527 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1528 selection.start.pos(), selection.end.pos(),
1529 bview->buffer()->params.textclass);
1533 void LyXText::pasteSelection(BufferView * bview)
1535 // this does not make sense, if there is nothing to paste
1536 if (!CutAndPaste::checkPastePossible(cursor.par()))
1539 setUndo(bview, Undo::INSERT,
1540 cursor.par(), cursor.par()->next());
1543 Paragraph * actpar = cursor.par();
1544 int pos = cursor.pos();
1546 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1547 bview->buffer()->params.textclass);
1549 redoParagraphs(bview, cursor, endpar);
1551 setCursor(bview, cursor.par(), cursor.pos());
1554 selection.cursor = cursor;
1555 setCursor(bview, actpar, pos);
1556 setSelection(bview);
1557 updateCounters(bview);
1561 void LyXText::setSelectionRange(BufferView * bview, lyx::pos_type length)
1566 selection.cursor = cursor;
1569 setSelection(bview);
1573 // simple replacing. The font of the first selected character is used
1574 void LyXText::replaceSelectionWithString(BufferView * bview,
1577 setCursorParUndo(bview);
1580 if (!selection.set()) { // create a dummy selection
1581 selection.end = cursor;
1582 selection.start = cursor;
1585 // Get font setting before we cut
1586 pos_type pos = selection.end.pos();
1587 LyXFont const font = selection.start.par()
1588 ->getFontSettings(bview->buffer()->params,
1589 selection.start.pos());
1591 // Insert the new string
1592 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1593 selection.end.par()->insertChar(pos, (*cit), font);
1597 // Cut the selection
1598 cutSelection(bview, true, false);
1604 // needed to insert the selection
1605 void LyXText::insertStringAsLines(BufferView * bview, string const & str)
1607 Paragraph * par = cursor.par();
1608 pos_type pos = cursor.pos();
1609 Paragraph * endpar = cursor.par()->next();
1611 setCursorParUndo(bview);
1613 // only to be sure, should not be neccessary
1616 bview->buffer()->insertStringAsLines(par, pos, current_font, str);
1618 redoParagraphs(bview, cursor, endpar);
1619 setCursor(bview, cursor.par(), cursor.pos());
1620 selection.cursor = cursor;
1621 setCursor(bview, par, pos);
1622 setSelection(bview);
1626 // turns double-CR to single CR, others where converted into one
1627 // blank. Then InsertStringAsLines is called
1628 void LyXText::insertStringAsParagraphs(BufferView * bview, string const & str)
1630 string linestr(str);
1631 bool newline_inserted = false;
1632 for (string::size_type i = 0; i < linestr.length(); ++i) {
1633 if (linestr[i] == '\n') {
1634 if (newline_inserted) {
1635 // we know that \r will be ignored by
1636 // InsertStringA. Of course, it is a dirty
1637 // trick, but it works...
1638 linestr[i - 1] = '\r';
1642 newline_inserted = true;
1644 } else if (IsPrintable(linestr[i])) {
1645 newline_inserted = false;
1648 insertStringAsLines(bview, linestr);
1652 void LyXText::checkParagraph(BufferView * bview, Paragraph * par,
1655 LyXCursor tmpcursor;
1659 Row * row = getRow(par, pos, y);
1661 // is there a break one row above
1662 if (row->previous() && row->previous()->par() == row->par()) {
1663 z = nextBreakPoint(bview, row->previous(), workWidth(bview));
1664 if (z >= row->pos()) {
1665 // set the dimensions of the row above
1666 y -= row->previous()->height();
1668 refresh_row = row->previous();
1669 status(bview, LyXText::NEED_MORE_REFRESH);
1671 breakAgain(bview, row->previous());
1673 // set the cursor again. Otherwise
1674 // dangling pointers are possible
1675 setCursor(bview, cursor.par(), cursor.pos(),
1676 false, cursor.boundary());
1677 selection.cursor = cursor;
1682 int const tmpheight = row->height();
1683 pos_type const tmplast = rowLast(row);
1687 breakAgain(bview, row);
1688 if (row->height() == tmpheight && rowLast(row) == tmplast)
1689 status(bview, LyXText::NEED_VERY_LITTLE_REFRESH);
1691 status(bview, LyXText::NEED_MORE_REFRESH);
1693 // check the special right address boxes
1694 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1701 redoDrawingOfParagraph(bview, tmpcursor);
1704 // set the cursor again. Otherwise dangling pointers are possible
1705 // also set the selection
1707 if (selection.set()) {
1709 setCursorIntern(bview, selection.cursor.par(), selection.cursor.pos(),
1710 false, selection.cursor.boundary());
1711 selection.cursor = cursor;
1712 setCursorIntern(bview, selection.start.par(),
1713 selection.start.pos(),
1714 false, selection.start.boundary());
1715 selection.start = cursor;
1716 setCursorIntern(bview, selection.end.par(),
1717 selection.end.pos(),
1718 false, selection.end.boundary());
1719 selection.end = cursor;
1720 setCursorIntern(bview, last_sel_cursor.par(),
1721 last_sel_cursor.pos(),
1722 false, last_sel_cursor.boundary());
1723 last_sel_cursor = cursor;
1726 setCursorIntern(bview, cursor.par(), cursor.pos(),
1727 false, cursor.boundary());
1731 // returns false if inset wasn't found
1732 bool LyXText::updateInset(BufferView * bview, Inset * inset)
1734 // first check the current paragraph
1735 int pos = cursor.par()->getPositionOfInset(inset);
1737 checkParagraph(bview, cursor.par(), pos);
1741 // check every paragraph
1743 Paragraph * par = ownerParagraph();
1745 pos = par->getPositionOfInset(inset);
1747 checkParagraph(bview, par, pos);
1757 bool LyXText::setCursor(BufferView * bview, Paragraph * par,
1759 bool setfont, bool boundary) const
1761 LyXCursor old_cursor = cursor;
1762 setCursorIntern(bview, par, pos, setfont, boundary);
1763 return deleteEmptyParagraphMechanism(bview, old_cursor);
1767 void LyXText::setCursor(BufferView * bview, LyXCursor & cur, Paragraph * par,
1768 pos_type pos, bool boundary) const
1775 cur.boundary(boundary);
1777 // get the cursor y position in text
1779 Row * row = getRow(par, pos, y);
1780 Row * old_row = row;
1782 // if we are before the first char of this row and are still in the
1783 // same paragraph and there is a previous row then put the cursor on
1784 // the end of the previous row
1785 cur.iy(y + row->baseline());
1787 if (row->previous() && pos &&
1788 row->previous()->par() == row->par() &&
1789 par->getChar(pos) == Paragraph::META_INSET &&
1790 (ins=par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1792 row = row->previous();
1797 // y is now the beginning of the cursor row
1798 y += row->baseline();
1799 // y is now the cursor baseline
1802 pos_type last = rowLastPrintable(old_row);
1804 // None of these should happen, but we're scaredy-cats
1805 if (pos > par->size()) {
1808 } else if (pos > last + 1) {
1809 // This shouldn't happen.
1812 } else if (pos < row->pos()) {
1817 // now get the cursors x position
1818 float x = getCursorX(bview, row, pos, last, boundary);
1821 if (old_row != row) {
1822 x = getCursorX(bview, old_row, pos, last, boundary);
1829 float LyXText::getCursorX(BufferView * bview, Row * row,
1830 pos_type pos, pos_type last, bool boundary) const
1832 pos_type cursor_vpos = 0;
1834 float fill_separator;
1836 float fill_label_hfill;
1837 // This call HAS to be here because of the BidiTables!!!
1838 prepareToPrint(bview, row, x, fill_separator, fill_hfill,
1841 if (last < row->pos())
1842 cursor_vpos = row->pos();
1843 else if (pos > last && !boundary)
1844 cursor_vpos = (row->par()->isRightToLeftPar(bview->buffer()->params))
1845 ? row->pos() : last + 1;
1846 else if (pos > row->pos() &&
1847 (pos > last || boundary))
1848 /// Place cursor after char at (logical) position pos - 1
1849 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1850 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1852 /// Place cursor before char at (logical) position pos
1853 cursor_vpos = (bidi_level(pos) % 2 == 0)
1854 ? log2vis(pos) : log2vis(pos) + 1;
1856 pos_type main_body =
1857 beginningOfMainBody(bview->buffer(), row->par());
1858 if ((main_body > 0) &&
1859 ((main_body-1 > last) ||
1860 !row->par()->isLineSeparator(main_body-1)))
1863 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1864 pos_type pos = vis2log(vpos);
1865 if (main_body > 0 && pos == main_body - 1) {
1866 x += fill_label_hfill +
1867 font_metrics::width(
1868 row->par()->layout()->labelsep,
1869 getLabelFont(bview->buffer(),
1871 if (row->par()->isLineSeparator(main_body - 1))
1872 x -= singleWidth(bview,
1873 row->par(), main_body - 1);
1875 if (hfillExpansion(bview->buffer(), row, pos)) {
1876 x += singleWidth(bview, row->par(), pos);
1877 if (pos >= main_body)
1880 x += fill_label_hfill;
1881 } else if (row->par()->isSeparator(pos)) {
1882 x += singleWidth(bview, row->par(), pos);
1883 if (pos >= main_body)
1884 x += fill_separator;
1886 x += singleWidth(bview, row->par(), pos);
1892 void LyXText::setCursorIntern(BufferView * bview, Paragraph * par,
1893 pos_type pos, bool setfont, bool boundary) const
1895 InsetText * it = static_cast<InsetText *>(par->inInset());
1897 if (it != inset_owner) {
1898 lyxerr[Debug::INSETS] << "InsetText is " << it
1900 << "inset_owner is "
1901 << inset_owner << endl;
1902 #ifdef WITH_WARNINGS
1903 #warning I believe this code is wrong. (Lgb)
1904 #warning Jürgen, have a look at this. (Lgb)
1905 #warning Hmmm, I guess you are right but we
1906 #warning should verify when this is needed
1908 // Jürgen, would you like to have a look?
1909 // I guess we need to move the outer cursor
1910 // and open and lock the inset (bla bla bla)
1911 // stuff I don't know... so can you have a look?
1913 // I moved the lyxerr stuff in here so we can see if
1914 // this is actually really needed and where!
1916 // it->getLyXText(bview)->setCursorIntern(bview, par, pos, setfont, boundary);
1921 setCursor(bview, cursor, par, pos, boundary);
1923 setCurrentFont(bview);
1927 void LyXText::setCurrentFont(BufferView * bview) const
1929 pos_type pos = cursor.pos();
1930 if (cursor.boundary() && pos > 0)
1934 if (pos == cursor.par()->size())
1936 else // potentional bug... BUG (Lgb)
1937 if (cursor.par()->isSeparator(pos)) {
1938 if (pos > cursor.row()->pos() &&
1939 bidi_level(pos) % 2 ==
1940 bidi_level(pos - 1) % 2)
1942 else if (pos + 1 < cursor.par()->size())
1948 cursor.par()->getFontSettings(bview->buffer()->params, pos);
1949 real_current_font = getFont(bview->buffer(), cursor.par(), pos);
1951 if (cursor.pos() == cursor.par()->size() &&
1952 isBoundary(bview->buffer(), cursor.par(), cursor.pos()) &&
1953 !cursor.boundary()) {
1954 Language const * lang =
1955 cursor.par()->getParLanguage(bview->buffer()->params);
1956 current_font.setLanguage(lang);
1957 current_font.setNumber(LyXFont::OFF);
1958 real_current_font.setLanguage(lang);
1959 real_current_font.setNumber(LyXFont::OFF);
1964 void LyXText::setCursorFromCoordinates(BufferView * bview, int x, int y) const
1966 LyXCursor old_cursor = cursor;
1968 setCursorFromCoordinates(bview, cursor, x, y);
1969 setCurrentFont(bview);
1970 deleteEmptyParagraphMechanism(bview, old_cursor);
1977 * return true if the cursor given is at the end of a row,
1978 * and the next row is filled by an inset that spans an entire
1981 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
1984 Row const & next = *row.next();
1986 if (next.pos() != cur.pos() || next.par() != cur.par())
1988 if (!cur.par()->isInset(cur.pos()))
1990 Inset const * inset = cur.par()->getInset(cur.pos());
1991 if (inset->needFullRow() || inset->display())
1998 void LyXText::setCursorFromCoordinates(BufferView * bview, LyXCursor & cur,
2001 // Get the row first.
2003 Row * row = getRowNearY(y);
2005 pos_type const column = getColumnNearX(bview, row, x, bound);
2006 cur.par(row->par());
2007 cur.pos(row->pos() + column);
2009 cur.y(y + row->baseline());
2012 if (beforeFullRowInset(*row, cur)) {
2013 pos_type last = rowLastPrintable(row);
2014 float x = getCursorX(bview, row->next(), cur.pos(), last, bound);
2016 cur.iy(y + row->height() + row->next()->baseline());
2017 cur.irow(row->next());
2023 cur.boundary(bound);
2027 void LyXText::cursorLeft(BufferView * bview, bool internal) const
2029 if (cursor.pos() > 0) {
2030 bool boundary = cursor.boundary();
2031 setCursor(bview, cursor.par(), cursor.pos() - 1, true, false);
2032 if (!internal && !boundary &&
2033 isBoundary(bview->buffer(), cursor.par(), cursor.pos() + 1))
2034 setCursor(bview, cursor.par(), cursor.pos() + 1, true, true);
2035 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2036 Paragraph * par = cursor.par()->previous();
2037 setCursor(bview, par, par->size());
2042 void LyXText::cursorRight(BufferView * bview, bool internal) const
2044 if (!internal && cursor.boundary() &&
2045 !cursor.par()->isNewline(cursor.pos()))
2046 setCursor(bview, cursor.par(), cursor.pos(), true, false);
2047 else if (cursor.pos() < cursor.par()->size()) {
2048 setCursor(bview, cursor.par(), cursor.pos() + 1, true, false);
2050 isBoundary(bview->buffer(), cursor.par(), cursor.pos()))
2051 setCursor(bview, cursor.par(), cursor.pos(), true, true);
2052 } else if (cursor.par()->next())
2053 setCursor(bview, cursor.par()->next(), 0);
2057 void LyXText::cursorUp(BufferView * bview, bool selecting) const
2060 int x = cursor.x_fix();
2061 int y = cursor.y() - cursor.row()->baseline() - 1;
2062 setCursorFromCoordinates(bview, x, y);
2064 int y1 = cursor.iy() - first_y;
2067 Inset * inset_hit = checkInsetHit(bview, x, y1);
2068 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2069 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2073 setCursorFromCoordinates(bview, cursor.x_fix(),
2074 cursor.y() - cursor.row()->baseline() - 1);
2079 void LyXText::cursorDown(BufferView * bview, bool selecting) const
2082 int x = cursor.x_fix();
2083 int y = cursor.y() - cursor.row()->baseline() +
2084 cursor.row()->height() + 1;
2085 setCursorFromCoordinates(bview, x, y);
2086 if (!selecting && cursor.row() == cursor.irow()) {
2087 int y1 = cursor.iy() - first_y;
2090 Inset * inset_hit = checkInsetHit(bview, x, y1);
2091 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2092 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2096 setCursorFromCoordinates(bview, cursor.x_fix(),
2097 cursor.y() - cursor.row()->baseline()
2098 + cursor.row()->height() + 1);
2103 void LyXText::cursorUpParagraph(BufferView * bview) const
2105 if (cursor.pos() > 0) {
2106 setCursor(bview, cursor.par(), 0);
2108 else if (cursor.par()->previous()) {
2109 setCursor(bview, cursor.par()->previous(), 0);
2114 void LyXText::cursorDownParagraph(BufferView * bview) const
2116 if (cursor.par()->next()) {
2117 setCursor(bview, cursor.par()->next(), 0);
2119 setCursor(bview, cursor.par(), cursor.par()->size());
2123 // fix the cursor `cur' after a characters has been deleted at `where'
2124 // position. Called by deleteEmptyParagraphMechanism
2125 void LyXText::fixCursorAfterDelete(BufferView * bview,
2127 LyXCursor const & where) const
2129 // if cursor is not in the paragraph where the delete occured,
2131 if (cur.par() != where.par())
2134 // if cursor position is after the place where the delete occured,
2136 if (cur.pos() > where.pos())
2137 cur.pos(cur.pos()-1);
2139 // check also if we don't want to set the cursor on a spot behind the
2140 // pagragraph because we erased the last character.
2141 if (cur.pos() > cur.par()->size())
2142 cur.pos(cur.par()->size());
2144 // recompute row et al. for this cursor
2145 setCursor(bview, cur, cur.par(), cur.pos(), cur.boundary());
2149 bool LyXText::deleteEmptyParagraphMechanism(BufferView * bview,
2150 LyXCursor const & old_cursor) const
2152 // Would be wrong to delete anything if we have a selection.
2153 if (selection.set())
2156 // We allow all kinds of "mumbo-jumbo" when freespacing.
2157 if (old_cursor.par()->layout()->free_spacing
2158 || old_cursor.par()->isFreeSpacing()) {
2162 /* Ok I'll put some comments here about what is missing.
2163 I have fixed BackSpace (and thus Delete) to not delete
2164 double-spaces automagically. I have also changed Cut,
2165 Copy and Paste to hopefully do some sensible things.
2166 There are still some small problems that can lead to
2167 double spaces stored in the document file or space at
2168 the beginning of paragraphs. This happens if you have
2169 the cursor betwenn to spaces and then save. Or if you
2170 cut and paste and the selection have a space at the
2171 beginning and then save right after the paste. I am
2172 sure none of these are very hard to fix, but I will
2173 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2174 that I can get some feedback. (Lgb)
2177 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2178 // delete the LineSeparator.
2181 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2182 // delete the LineSeparator.
2185 // If the pos around the old_cursor were spaces, delete one of them.
2186 if (old_cursor.par() != cursor.par()
2187 || old_cursor.pos() != cursor.pos()) {
2188 // Only if the cursor has really moved
2190 if (old_cursor.pos() > 0
2191 && old_cursor.pos() < old_cursor.par()->size()
2192 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2193 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2194 old_cursor.par()->erase(old_cursor.pos() - 1);
2195 redoParagraphs(bview, old_cursor, old_cursor.par()->next());
2197 #ifdef WITH_WARNINGS
2198 #warning This will not work anymore when we have multiple views of the same buffer
2199 // In this case, we will have to correct also the cursors held by
2200 // other bufferviews. It will probably be easier to do that in a more
2201 // automated way in LyXCursor code. (JMarc 26/09/2001)
2203 // correct all cursors held by the LyXText
2204 fixCursorAfterDelete(bview, cursor, old_cursor);
2205 fixCursorAfterDelete(bview, selection.cursor,
2207 fixCursorAfterDelete(bview, selection.start,
2209 fixCursorAfterDelete(bview, selection.end, old_cursor);
2210 fixCursorAfterDelete(bview, last_sel_cursor,
2212 fixCursorAfterDelete(bview, toggle_cursor, old_cursor);
2213 fixCursorAfterDelete(bview, toggle_end_cursor,
2219 // don't delete anything if this is the ONLY paragraph!
2220 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2223 // Do not delete empty paragraphs with keepempty set.
2224 if (old_cursor.par()->layout()->keepempty)
2227 // only do our magic if we changed paragraph
2228 if (old_cursor.par() == cursor.par())
2231 // record if we have deleted a paragraph
2232 // we can't possibly have deleted a paragraph before this point
2233 bool deleted = false;
2235 if ((old_cursor.par()->empty()
2236 || (old_cursor.par()->size() == 1
2237 && old_cursor.par()->isLineSeparator(0)))) {
2238 // ok, we will delete anything
2239 LyXCursor tmpcursor;
2241 // make sure that you do not delete any environments
2242 status(bview, LyXText::NEED_MORE_REFRESH);
2245 if (old_cursor.row()->previous()) {
2246 refresh_row = old_cursor.row()->previous();
2247 refresh_y = old_cursor.y() - old_cursor.row()->baseline() - refresh_row->height();
2249 cursor = old_cursor; // that undo can restore the right cursor position
2250 Paragraph * endpar = old_cursor.par()->next();
2251 if (endpar && endpar->getDepth()) {
2252 while (endpar && endpar->getDepth()) {
2253 endpar = endpar->next();
2256 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2260 removeRow(old_cursor.row());
2261 if (ownerParagraph() == old_cursor.par()) {
2262 ownerParagraph(ownerParagraph()->next());
2265 delete old_cursor.par();
2267 /* Breakagain the next par. Needed because of
2268 * the parindent that can occur or dissappear.
2269 * The next row can change its height, if
2270 * there is another layout before */
2271 if (refresh_row->next()) {
2272 breakAgain(bview, refresh_row->next());
2273 updateCounters(bview);
2275 setHeightOfRow(bview, refresh_row);
2277 refresh_row = old_cursor.row()->next();
2278 refresh_y = old_cursor.y() - old_cursor.row()->baseline();
2281 cursor = old_cursor; // that undo can restore the right cursor position
2282 Paragraph * endpar = old_cursor.par()->next();
2283 if (endpar && endpar->getDepth()) {
2284 while (endpar && endpar->getDepth()) {
2285 endpar = endpar->next();
2288 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2292 removeRow(old_cursor.row());
2294 if (ownerParagraph() == old_cursor.par()) {
2295 ownerParagraph(ownerParagraph()->next());
2298 delete old_cursor.par();
2300 /* Breakagain the next par. Needed because of
2301 the parindent that can occur or dissappear.
2302 The next row can change its height, if
2303 there is another layout before */
2305 breakAgain(bview, refresh_row);
2306 updateCounters(bview);
2311 setCursorIntern(bview, cursor.par(), cursor.pos());
2313 if (selection.cursor.par() == old_cursor.par()
2314 && selection.cursor.pos() == old_cursor.pos()) {
2315 // correct selection
2316 selection.cursor = cursor;
2320 if (old_cursor.par()->stripLeadingSpaces()) {
2321 redoParagraphs(bview, old_cursor,
2322 old_cursor.par()->next());
2324 setCursorIntern(bview, cursor.par(), cursor.pos());
2325 selection.cursor = cursor;
2332 Paragraph * LyXText::ownerParagraph() const
2335 return inset_owner->paragraph();
2337 return &*(bv_owner->buffer()->paragraphs.begin());
2341 void LyXText::ownerParagraph(Paragraph * p) const
2344 inset_owner->paragraph(p);
2346 bv_owner->buffer()->paragraphs.set(p);
2351 void LyXText::ownerParagraph(int id, Paragraph * p) const
2353 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2354 if (op && op->inInset()) {
2355 static_cast<InsetText *>(op->inInset())->paragraph(p);
2362 LyXText::text_status LyXText::status() const
2368 void LyXText::status(BufferView * bview, LyXText::text_status st) const
2370 LyXText * t = bview->text;
2372 // We should only go up with refreshing code so this means that if
2373 // we have a MORE refresh we should never set it to LITTLE if we still
2374 // didn't handle it (and then it will be UNCHANGED. Now as long as
2375 // we stay inside one LyXText this may work but we need to tell the
2376 // outermost LyXText that it should REALLY draw us if there is some
2377 // change in a Inset::LyXText. So you see that when we are inside a
2378 // inset's LyXText we give the LITTLE to the outermost LyXText to
2379 // tell'em that it should redraw the actual row (where the inset
2380 // resides! Capito?!
2382 if (status_ != NEED_MORE_REFRESH || st != NEED_VERY_LITTLE_REFRESH) {
2384 if (inset_owner && st != UNCHANGED) {
2385 t->status(bview, NEED_VERY_LITTLE_REFRESH);
2386 if (!t->refresh_row) {
2387 t->refresh_row = t->cursor.row();
2388 t->refresh_y = t->cursor.y() -
2389 t->cursor.row()->baseline();