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), top_row_(0), top_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), need_break_row(0),
57 refresh_y(0), refresh_row(0), bv_owner(bv),
58 status_(LyXText::UNCHANGED), firstrow(0), lastrow(0)
62 LyXText::LyXText(InsetText * inset)
63 : height(0), width(0), top_row_(0), top_row_offset_(0),
64 inset_owner(inset), the_locking_inset(0), need_break_row(0),
65 refresh_y(0), refresh_row(0), bv_owner(0),
66 status_(LyXText::UNCHANGED), firstrow(0), lastrow(0)
70 void LyXText::init(BufferView * bview, bool reinit)
73 // Delete all rows, this does not touch the paragraphs!
74 Row * tmprow = firstrow;
76 tmprow = firstrow->next();
84 copylayouttype.erase();
90 Paragraph * par = ownerParagraph();
91 current_font = getFont(bview->buffer(), par, 0);
94 insertParagraph(bview, par, lastrow);
97 setCursorIntern(bview, firstrow->par(), 0);
98 selection.cursor = cursor;
100 updateCounters(bview);
106 // Delete all rows, this does not touch the paragraphs!
107 Row * tmprow = firstrow;
109 tmprow = firstrow->next();
118 LyXFont const realizeFont(LyXFont const & font,
122 LyXTextClass const & tclass = buf->params.getLyXTextClass();
123 LyXFont tmpfont(font);
124 Paragraph::depth_type par_depth = par->getDepth();
126 // Resolve against environment font information
127 while (par && par_depth && !tmpfont.resolved()) {
128 par = par->outerHook();
130 tmpfont.realize(par->layout()->font);
131 par_depth = par->getDepth();
135 tmpfont.realize(tclass.defaultfont());
143 // Gets the fully instantiated font at a given position in a paragraph
144 // Basically the same routine as Paragraph::getFont() in paragraph.C.
145 // The difference is that this one is used for displaying, and thus we
146 // are allowed to make cosmetic improvements. For instance make footnotes
148 // If position is -1, we get the layout font of the paragraph.
149 // If position is -2, we get the font of the manual label of the paragraph.
150 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
153 lyx::Assert(pos >= 0);
155 LyXLayout_ptr const & layout = par->layout();
157 // We specialize the 95% common case:
158 if (!par->getDepth()) {
159 if (layout->labeltype == LABEL_MANUAL
160 && pos < par->beginningOfBody()) {
162 LyXFont f = par->getFontSettings(buf->params, pos);
164 par->inInset()->getDrawFont(f);
165 return f.realize(layout->reslabelfont);
167 LyXFont f = par->getFontSettings(buf->params, pos);
169 par->inInset()->getDrawFont(f);
170 return f.realize(layout->resfont);
174 // The uncommon case need not be optimized as much
178 if (pos < par->beginningOfBody()) {
180 layoutfont = layout->labelfont;
183 layoutfont = layout->font;
186 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
187 tmpfont.realize(layoutfont);
190 par->inInset()->getDrawFont(tmpfont);
192 return realizeFont(tmpfont, buf, par);
196 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
198 LyXLayout_ptr const & layout = par->layout();
200 if (!par->getDepth()) {
201 return layout->resfont;
204 return realizeFont(layout->font, buf, par);
208 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
210 LyXLayout_ptr const & layout = par->layout();
212 if (!par->getDepth()) {
213 return layout->reslabelfont;
216 return realizeFont(layout->labelfont, buf, par);
220 void LyXText::setCharFont(BufferView * bv, Paragraph * par,
221 pos_type pos, LyXFont const & fnt,
224 Buffer const * buf = bv->buffer();
225 LyXFont font = getFont(buf, par, pos);
226 font.update(fnt, buf->params.language, toggleall);
227 // Let the insets convert their font
228 if (par->isInset(pos)) {
229 Inset * inset = par->getInset(pos);
230 if (isEditableInset(inset)) {
231 UpdatableInset * uinset =
232 static_cast<UpdatableInset *>(inset);
233 uinset->setFont(bv, fnt, toggleall, true);
237 // Plug thru to version below:
238 setCharFont(buf, par, pos, font);
242 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
243 pos_type pos, LyXFont const & fnt)
247 LyXTextClass const & tclass = buf->params.getLyXTextClass();
248 LyXLayout_ptr const & layout = par->layout();
250 // Get concrete layout font to reduce against
253 if (pos < par->beginningOfBody())
254 layoutfont = layout->labelfont;
256 layoutfont = layout->font;
258 // Realize against environment font information
259 if (par->getDepth()) {
260 Paragraph * tp = par;
261 while (!layoutfont.resolved() && tp && tp->getDepth()) {
262 tp = tp->outerHook();
264 layoutfont.realize(tp->layout()->font);
268 layoutfont.realize(tclass.defaultfont());
270 // Now, reduce font against full layout font
271 font.reduce(layoutfont);
273 par->setFont(pos, font);
277 // inserts a new row before the specified row, increments
278 // the touched counters
279 void LyXText::insertRow(Row * row, Paragraph * par,
282 Row * tmprow = new Row;
285 tmprow->next(firstrow);
288 tmprow->previous(row);
289 tmprow->next(row->next());
294 tmprow->next()->previous(tmprow);
296 if (tmprow->previous())
297 tmprow->previous()->next(tmprow);
308 // removes the row and reset the touched counters
309 void LyXText::removeRow(Row * row) const
311 Row * row_prev = row->previous();
313 row->next()->previous(row_prev);
315 firstrow = row->next();
316 // lyx::Assert(firstrow);
318 row_prev->next(row->next());
320 if (row == lastrow) {
321 lyx::Assert(!row->next());
324 if (refresh_row == row) {
325 refresh_row = row_prev ? row_prev : row->next();
326 // what about refresh_y, refresh_height
328 if (top_row_ == row) {
330 top_row_ = row->next();
331 top_row_offset_ -= row->height();
338 height -= row->height(); // the text becomes smaller
344 // remove all following rows of the paragraph of the specified row.
345 void LyXText::removeParagraph(Row * row) const
347 Paragraph * tmppar = row->par();
351 while (row && row->par() == tmppar) {
352 tmprow = row->next();
359 // insert the specified paragraph behind the specified row
360 void LyXText::insertParagraph(BufferView * bview, Paragraph * par,
363 // insert a new row, starting at position 0
364 insertRow(row, par, 0);
366 // and now append the whole paragraph before the new row
369 appendParagraph(bview, firstrow);
371 row->next()->height(0);
372 appendParagraph(bview, row->next());
377 Inset * LyXText::getInset() const
379 if (cursor.pos() < cursor.par()->size()
380 && cursor.par()->isInset(cursor.pos())) {
381 return cursor.par()->getInset(cursor.pos());
387 void LyXText::toggleInset(BufferView * bview)
389 Inset * inset = getInset();
390 // is there an editable inset at cursor position?
391 if (!isEditableInset(inset)) {
392 // No, try to see if we are inside a collapsable inset
393 if (inset_owner && inset_owner->owner()
394 && inset_owner->owner()->isOpen()) {
395 bview->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
396 inset_owner->owner()->close(bview);
397 bview->getLyXText()->cursorRight(bview);
401 //bview->owner()->message(inset->editMessage());
403 // do we want to keep this?? (JMarc)
404 if (!isHighlyEditableInset(inset))
405 setCursorParUndo(bview);
407 if (inset->isOpen()) {
413 inset->open(bview, !inset->isOpen());
418 /* used in setlayout */
419 // Asger is not sure we want to do this...
420 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
423 LyXLayout_ptr const & layout = par.layout();
426 for (pos_type pos = 0; pos < par.size(); ++pos) {
427 if (pos < par.beginningOfBody())
428 layoutfont = layout->labelfont;
430 layoutfont = layout->font;
432 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
433 tmpfont.reduce(layoutfont);
434 par.setFont(pos, tmpfont);
439 Paragraph * LyXText::setLayout(BufferView * bview,
440 LyXCursor & cur, LyXCursor & sstart_cur,
441 LyXCursor & send_cur,
442 string const & layout)
444 Paragraph * endpar = send_cur.par()->next();
445 Paragraph * undoendpar = endpar;
447 if (endpar && endpar->getDepth()) {
448 while (endpar && endpar->getDepth()) {
449 endpar = endpar->next();
453 endpar = endpar->next(); // because of parindents etc.
456 setUndo(bview, Undo::EDIT, sstart_cur.par(), undoendpar);
458 // ok we have a selection. This is always between sstart_cur
459 // and sel_end cursor
461 Paragraph * par = sstart_cur.par();
462 Paragraph * epar = send_cur.par()->next();
464 LyXLayout_ptr const & lyxlayout =
465 bview->buffer()->params.getLyXTextClass()[layout];
468 par->applyLayout(lyxlayout);
469 makeFontEntriesLayoutSpecific(*bview->buffer(), *par);
470 Paragraph * fppar = par;
471 fppar->params().spaceTop(lyxlayout->fill_top ?
472 VSpace(VSpace::VFILL)
473 : VSpace(VSpace::NONE));
474 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
475 VSpace(VSpace::VFILL)
476 : VSpace(VSpace::NONE));
477 if (lyxlayout->margintype == MARGIN_MANUAL)
478 par->setLabelWidthString(lyxlayout->labelstring());
481 } while (par != epar);
487 // set layout over selection and make a total rebreak of those paragraphs
488 void LyXText::setLayout(BufferView * bview, string const & layout)
490 LyXCursor tmpcursor = cursor; /* store the current cursor */
492 // if there is no selection just set the layout
493 // of the current paragraph */
494 if (!selection.set()) {
495 selection.start = cursor; // dummy selection
496 selection.end = cursor;
498 Paragraph * endpar = setLayout(bview, cursor, selection.start,
499 selection.end, layout);
500 redoParagraphs(bview, selection.start, endpar);
502 // we have to reset the selection, because the
503 // geometry could have changed
504 setCursor(bview, selection.start.par(),
505 selection.start.pos(), false);
506 selection.cursor = cursor;
507 setCursor(bview, selection.end.par(), selection.end.pos(), false);
508 updateCounters(bview);
511 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true);
515 // increment depth over selection and
516 // make a total rebreak of those paragraphs
517 void LyXText::incDepth(BufferView * bview)
519 // If there is no selection, just use the current paragraph
520 if (!selection.set()) {
521 selection.start = cursor; // dummy selection
522 selection.end = cursor;
525 // We end at the next paragraph with depth 0
526 Paragraph * endpar = selection.end.par()->next();
528 Paragraph * undoendpar = endpar;
530 if (endpar && endpar->getDepth()) {
531 while (endpar && endpar->getDepth()) {
532 endpar = endpar->next();
536 endpar = endpar->next(); // because of parindents etc.
539 setUndo(bview, Undo::EDIT,
540 selection.start.par(), undoendpar);
542 LyXCursor tmpcursor = cursor; // store the current cursor
544 // ok we have a selection. This is always between sel_start_cursor
545 // and sel_end cursor
546 cursor = selection.start;
549 // NOTE: you can't change the depth of a bibliography entry
550 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
551 Paragraph * prev = cursor.par()->previous();
554 if (cursor.par()->getDepth()
555 < prev->getMaxDepthAfter()) {
556 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
560 if (cursor.par() == selection.end.par())
562 cursor.par(cursor.par()->next());
565 redoParagraphs(bview, selection.start, endpar);
567 // we have to reset the selection, because the
568 // geometry could have changed
569 setCursor(bview, selection.start.par(), selection.start.pos());
570 selection.cursor = cursor;
571 setCursor(bview, selection.end.par(), selection.end.pos());
572 updateCounters(bview);
575 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
579 // decrement depth over selection and
580 // make a total rebreak of those paragraphs
581 void LyXText::decDepth(BufferView * bview)
583 // if there is no selection just set the layout
584 // of the current paragraph
585 if (!selection.set()) {
586 selection.start = cursor; // dummy selection
587 selection.end = cursor;
589 Paragraph * endpar = selection.end.par()->next();
590 Paragraph * undoendpar = endpar;
592 if (endpar && endpar->getDepth()) {
593 while (endpar && endpar->getDepth()) {
594 endpar = endpar->next();
598 endpar = endpar->next(); // because of parindents etc.
601 setUndo(bview, Undo::EDIT,
602 selection.start.par(), undoendpar);
604 LyXCursor tmpcursor = cursor; // store the current cursor
606 // ok we have a selection. This is always between sel_start_cursor
607 // and sel_end cursor
608 cursor = selection.start;
611 if (cursor.par()->params().depth()) {
612 cursor.par()->params()
613 .depth(cursor.par()->params().depth() - 1);
615 if (cursor.par() == selection.end.par()) {
618 cursor.par(cursor.par()->next());
621 redoParagraphs(bview, selection.start, endpar);
623 // we have to reset the selection, because the
624 // geometry could have changed
625 setCursor(bview, selection.start.par(),
626 selection.start.pos());
627 selection.cursor = cursor;
628 setCursor(bview, selection.end.par(), selection.end.pos());
629 updateCounters(bview);
632 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
636 // set font over selection and make a total rebreak of those paragraphs
637 void LyXText::setFont(BufferView * bview, LyXFont const & font, bool toggleall)
639 // if there is no selection just set the current_font
640 if (!selection.set()) {
641 // Determine basis font
643 if (cursor.pos() < cursor.par()->beginningOfBody()) {
644 layoutfont = getLabelFont(bview->buffer(),
647 layoutfont = getLayoutFont(bview->buffer(),
650 // Update current font
651 real_current_font.update(font,
652 bview->buffer()->params.language,
655 // Reduce to implicit settings
656 current_font = real_current_font;
657 current_font.reduce(layoutfont);
658 // And resolve it completely
659 real_current_font.realize(layoutfont);
664 LyXCursor tmpcursor = cursor; // store the current cursor
666 // ok we have a selection. This is always between sel_start_cursor
667 // and sel_end cursor
669 setUndo(bview, Undo::EDIT,
670 selection.start.par(), selection.end.par()->next());
672 cursor = selection.start;
673 while (cursor.par() != selection.end.par() ||
674 cursor.pos() < selection.end.pos())
676 if (cursor.pos() < cursor.par()->size()) {
677 // an open footnote should behave like a closed one
678 setCharFont(bview, cursor.par(), cursor.pos(),
680 cursor.pos(cursor.pos() + 1);
683 cursor.par(cursor.par()->next());
688 redoParagraphs(bview, selection.start, selection.end.par()->next());
690 // we have to reset the selection, because the
691 // geometry could have changed, but we keep
692 // it for user convenience
693 setCursor(bview, selection.start.par(), selection.start.pos());
694 selection.cursor = cursor;
695 setCursor(bview, selection.end.par(), selection.end.pos());
697 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true,
698 tmpcursor.boundary());
702 void LyXText::redoHeightOfParagraph(BufferView * bview)
704 Row * tmprow = cursor.row();
705 int y = cursor.y() - tmprow->baseline();
707 setHeightOfRow(bview, tmprow);
709 while (tmprow->previous()
710 && tmprow->previous()->par() == tmprow->par()) {
711 tmprow = tmprow->previous();
712 y -= tmprow->height();
713 setHeightOfRow(bview, tmprow);
716 // we can set the refreshing parameters now
717 status(bview, LyXText::NEED_MORE_REFRESH);
719 refresh_row = tmprow;
720 setCursor(bview, cursor.par(), cursor.pos(), false, cursor.boundary());
724 void LyXText::redoDrawingOfParagraph(BufferView * bview, LyXCursor const & cur)
726 Row * tmprow = cur.row();
728 int y = cur.y() - tmprow->baseline();
729 setHeightOfRow(bview, tmprow);
731 while (tmprow->previous()
732 && tmprow->previous()->par() == tmprow->par()) {
733 tmprow = tmprow->previous();
734 y -= tmprow->height();
737 // we can set the refreshing parameters now
738 if (status_ == LyXText::UNCHANGED || y < refresh_y) {
740 refresh_row = tmprow;
742 status(bview, LyXText::NEED_MORE_REFRESH);
743 setCursor(bview, cur.par(), cur.pos());
747 // deletes and inserts again all paragaphs between the cursor
748 // and the specified par
749 // This function is needed after SetLayout and SetFont etc.
750 void LyXText::redoParagraphs(BufferView * bview, LyXCursor const & cur,
751 Paragraph const * endpar) const
754 Paragraph * tmppar = 0;
755 Paragraph * first_phys_par = 0;
757 Row * tmprow = cur.row();
759 int y = cur.y() - tmprow->baseline();
761 if (!tmprow->previous()) {
762 // a trick/hack for UNDO
763 // This is needed because in an UNDO/REDO we could have changed
764 // the ownerParagrah() so the paragraph inside the row is NOT
765 // my really first par anymore. Got it Lars ;) (Jug 20011206)
766 first_phys_par = ownerParagraph();
768 first_phys_par = tmprow->par();
769 while (tmprow->previous()
770 && tmprow->previous()->par() == first_phys_par)
772 tmprow = tmprow->previous();
773 y -= tmprow->height();
777 // we can set the refreshing parameters now
778 status(bview, LyXText::NEED_MORE_REFRESH);
780 refresh_row = tmprow->previous(); /* the real refresh row will
781 be deleted, so I store
785 tmppar = tmprow->next()->par();
788 while (tmprow->next() && tmppar != endpar) {
789 removeRow(tmprow->next());
790 if (tmprow->next()) {
791 tmppar = tmprow->next()->par();
797 // remove the first one
798 tmprow2 = tmprow; /* this is because tmprow->previous()
800 tmprow = tmprow->previous();
803 tmppar = first_phys_par;
807 insertParagraph(bview, tmppar, tmprow);
811 while (tmprow->next()
812 && tmprow->next()->par() == tmppar) {
813 tmprow = tmprow->next();
815 tmppar = tmppar->next();
817 } while (tmppar && tmppar != endpar);
819 // this is because of layout changes
821 refresh_y -= refresh_row->height();
822 setHeightOfRow(bview, refresh_row);
824 refresh_row = firstrow;
826 setHeightOfRow(bview, refresh_row);
829 if (tmprow && tmprow->next())
830 setHeightOfRow(bview, tmprow->next());
831 updateCounters(bview);
835 void LyXText::fullRebreak(BufferView * bview)
841 if (need_break_row) {
842 breakAgain(bview, need_break_row);
849 // important for the screen
852 // the cursor set functions have a special mechanism. When they
853 // realize, that you left an empty paragraph, they will delete it.
854 // They also delete the corresponding row
856 // need the selection cursor:
857 void LyXText::setSelection(BufferView * bview)
859 bool const lsel = selection.set();
861 if (!selection.set()) {
862 last_sel_cursor = selection.cursor;
863 selection.start = selection.cursor;
864 selection.end = selection.cursor;
869 // first the toggling area
870 if (cursor.y() < last_sel_cursor.y()
871 || (cursor.y() == last_sel_cursor.y()
872 && cursor.x() < last_sel_cursor.x())) {
873 toggle_end_cursor = last_sel_cursor;
874 toggle_cursor = cursor;
876 toggle_end_cursor = cursor;
877 toggle_cursor = last_sel_cursor;
880 last_sel_cursor = cursor;
882 // and now the whole selection
884 if (selection.cursor.par() == cursor.par())
885 if (selection.cursor.pos() < cursor.pos()) {
886 selection.end = cursor;
887 selection.start = selection.cursor;
889 selection.end = selection.cursor;
890 selection.start = cursor;
892 else if (selection.cursor.y() < cursor.y() ||
893 (selection.cursor.y() == cursor.y()
894 && selection.cursor.x() < cursor.x())) {
895 selection.end = cursor;
896 selection.start = selection.cursor;
899 selection.end = selection.cursor;
900 selection.start = cursor;
903 // a selection with no contents is not a selection
904 if (selection.start.par() == selection.end.par() &&
905 selection.start.pos() == selection.end.pos())
906 selection.set(false);
908 if (inset_owner && (selection.set() || lsel))
909 inset_owner->setUpdateStatus(bview, InsetText::SELECTION);
913 string const LyXText::selectionAsString(Buffer const * buffer,
916 if (!selection.set()) return string();
918 // should be const ...
919 Paragraph * startpar(selection.start.par());
920 Paragraph * endpar(selection.end.par());
921 pos_type const startpos(selection.start.pos());
922 pos_type const endpos(selection.end.pos());
924 if (startpar == endpar) {
925 return startpar->asString(buffer, startpos, endpos, label);
930 // First paragraph in selection
931 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
933 // The paragraphs in between (if any)
934 LyXCursor tmpcur(selection.start);
935 tmpcur.par(tmpcur.par()->next());
936 while (tmpcur.par() != endpar) {
937 result += tmpcur.par()->asString(buffer, 0,
938 tmpcur.par()->size(),
940 tmpcur.par(tmpcur.par()->next());
943 // Last paragraph in selection
944 result += endpar->asString(buffer, 0, endpos, label);
950 void LyXText::clearSelection() const
952 selection.set(false);
953 selection.mark(false);
954 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
955 // reset this in the bv_owner!
956 if (bv_owner && bv_owner->text)
957 bv_owner->text->xsel_cache.set(false);
961 void LyXText::cursorHome(BufferView * bview) const
963 setCursor(bview, cursor.par(), cursor.row()->pos());
967 void LyXText::cursorEnd(BufferView * bview) const
969 if (!cursor.row()->next()
970 || cursor.row()->next()->par() != cursor.row()->par()) {
971 setCursor(bview, cursor.par(), cursor.row()->lastPos() + 1);
973 if (!cursor.par()->empty() &&
974 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
975 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
976 setCursor(bview, cursor.par(), cursor.row()->lastPos());
978 setCursor(bview,cursor.par(),
979 cursor.row()->lastPos() + 1);
985 void LyXText::cursorTop(BufferView * bview) const
987 while (cursor.par()->previous())
988 cursor.par(cursor.par()->previous());
989 setCursor(bview, cursor.par(), 0);
993 void LyXText::cursorBottom(BufferView * bview) const
995 while (cursor.par()->next())
996 cursor.par(cursor.par()->next());
997 setCursor(bview, cursor.par(), cursor.par()->size());
1001 void LyXText::toggleFree(BufferView * bview,
1002 LyXFont const & font, bool toggleall)
1004 // If the mask is completely neutral, tell user
1005 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
1006 // Could only happen with user style
1007 bview->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
1011 // Try implicit word selection
1012 // If there is a change in the language the implicit word selection
1014 LyXCursor resetCursor = cursor;
1015 bool implicitSelection = (font.language() == ignore_language
1016 && font.number() == LyXFont::IGNORE)
1017 ? selectWordWhenUnderCursor(bview, WHOLE_WORD_STRICT) : false;
1020 setFont(bview, font, toggleall);
1022 // Implicit selections are cleared afterwards
1023 //and cursor is set to the original position.
1024 if (implicitSelection) {
1026 cursor = resetCursor;
1027 setCursor(bview, cursor.par(), cursor.pos());
1028 selection.cursor = cursor;
1031 inset_owner->setUpdateStatus(bview, InsetText::CURSOR_PAR);
1035 string LyXText::getStringToIndex(BufferView * bview)
1037 // Try implicit word selection
1038 // If there is a change in the language the implicit word selection
1040 LyXCursor const reset_cursor = cursor;
1041 bool const implicitSelection = selectWordWhenUnderCursor(bview, PREVIOUS_WORD);
1044 if (!selection.set())
1045 bview->owner()->message(_("Nothing to index!"));
1046 else if (selection.start.par() != selection.end.par())
1047 bview->owner()->message(_("Cannot index more than one paragraph!"));
1049 idxstring = selectionAsString(bview->buffer(), false);
1051 // Reset cursors to their original position.
1052 cursor = reset_cursor;
1053 setCursor(bview, cursor.par(), cursor.pos());
1054 selection.cursor = cursor;
1056 // Clear the implicit selection.
1057 if (implicitSelection)
1064 // the DTP switches for paragraphs. LyX will store them in the first
1065 // physicla paragraph. When a paragraph is broken, the top settings rest,
1066 // the bottom settings are given to the new one. So I can make shure,
1067 // they do not duplicate themself and you cannnot make dirty things with
1070 void LyXText::setParagraph(BufferView * bview,
1071 bool line_top, bool line_bottom,
1072 bool pagebreak_top, bool pagebreak_bottom,
1073 VSpace const & space_top,
1074 VSpace const & space_bottom,
1075 Spacing const & spacing,
1077 string labelwidthstring,
1080 LyXCursor tmpcursor = cursor;
1081 if (!selection.set()) {
1082 selection.start = cursor;
1083 selection.end = cursor;
1086 // make sure that the depth behind the selection are restored, too
1087 Paragraph * endpar = selection.end.par()->next();
1088 Paragraph * undoendpar = endpar;
1090 if (endpar && endpar->getDepth()) {
1091 while (endpar && endpar->getDepth()) {
1092 endpar = endpar->next();
1093 undoendpar = endpar;
1097 // because of parindents etc.
1098 endpar = endpar->next();
1101 setUndo(bview, Undo::EDIT, selection.start.par(), undoendpar);
1104 Paragraph * tmppar = selection.end.par();
1106 while (tmppar != selection.start.par()->previous()) {
1107 setCursor(bview, tmppar, 0);
1108 status(bview, LyXText::NEED_MORE_REFRESH);
1109 refresh_row = cursor.row();
1110 refresh_y = cursor.y() - cursor.row()->baseline();
1111 cursor.par()->params().lineTop(line_top);
1112 cursor.par()->params().lineBottom(line_bottom);
1113 cursor.par()->params().pagebreakTop(pagebreak_top);
1114 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1115 cursor.par()->params().spaceTop(space_top);
1116 cursor.par()->params().spaceBottom(space_bottom);
1117 cursor.par()->params().spacing(spacing);
1118 // does the layout allow the new alignment?
1119 LyXLayout_ptr const & layout = cursor.par()->layout();
1121 if (align == LYX_ALIGN_LAYOUT)
1122 align = layout->align;
1123 if (align & layout->alignpossible) {
1124 if (align == layout->align)
1125 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1127 cursor.par()->params().align(align);
1129 cursor.par()->setLabelWidthString(labelwidthstring);
1130 cursor.par()->params().noindent(noindent);
1131 tmppar = cursor.par()->previous();
1134 redoParagraphs(bview, selection.start, endpar);
1137 setCursor(bview, selection.start.par(), selection.start.pos());
1138 selection.cursor = cursor;
1139 setCursor(bview, selection.end.par(), selection.end.pos());
1140 setSelection(bview);
1141 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
1143 bview->updateInset(inset_owner, true);
1147 // set the counter of a paragraph. This includes the labels
1148 void LyXText::setCounter(Buffer const * buf, Paragraph * par) const
1150 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1151 LyXLayout_ptr const & layout = par->layout();
1153 if (par->previous()) {
1155 par->params().appendix(par->previous()->params().appendix());
1156 if (!par->params().appendix() && par->params().startOfAppendix()) {
1157 par->params().appendix(true);
1158 textclass.counters().reset();
1160 par->enumdepth = par->previous()->enumdepth;
1161 par->itemdepth = par->previous()->itemdepth;
1163 par->params().appendix(par->params().startOfAppendix());
1168 /* Maybe we have to increment the enumeration depth.
1169 * BUT, enumeration in a footnote is considered in isolation from its
1170 * surrounding paragraph so don't increment if this is the
1171 * first line of the footnote
1172 * AND, bibliographies can't have their depth changed ie. they
1173 * are always of depth 0
1176 && par->previous()->getDepth() < par->getDepth()
1177 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1178 && par->enumdepth < 3
1179 && layout->labeltype != LABEL_BIBLIO) {
1183 // Maybe we have to decrement the enumeration depth, see note above
1185 && par->previous()->getDepth() > par->getDepth()
1186 && layout->labeltype != LABEL_BIBLIO) {
1187 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1190 if (!par->params().labelString().empty()) {
1191 par->params().labelString(string());
1194 if (layout->margintype == MARGIN_MANUAL) {
1195 if (par->params().labelWidthString().empty()) {
1196 par->setLabelWidthString(layout->labelstring());
1199 par->setLabelWidthString(string());
1202 // is it a layout that has an automatic label?
1203 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1204 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1208 if (i >= 0 && i <= buf->params.secnumdepth) {
1212 textclass.counters().step(layout->latexname());
1214 // Is there a label? Useful for Chapter layout
1215 if (!par->params().appendix()) {
1216 s << layout->labelstring();
1218 s << layout->labelstring_appendix();
1221 // Use of an integer is here less than elegant. For now.
1222 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1223 if (!par->params().appendix()) {
1224 numbertype = "sectioning";
1226 numbertype = "appendix";
1227 if (par->isRightToLeftPar(buf->params))
1228 langtype = "hebrew";
1233 s << textclass.counters()
1234 .numberLabel(layout->latexname(),
1235 numbertype, langtype, head);
1237 par->params().labelString(STRCONV(s.str()));
1239 // reset enum counters
1240 textclass.counters().reset("enum");
1241 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1242 textclass.counters().reset("enum");
1243 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1245 // Yes I know this is a really, really! bad solution
1247 string enumcounter("enum");
1249 switch (par->enumdepth) {
1258 enumcounter += "iv";
1261 // not a valid enumdepth...
1265 textclass.counters().step(enumcounter);
1267 s << textclass.counters()
1268 .numberLabel(enumcounter, "enumeration");
1269 par->params().labelString(STRCONV(s.str()));
1271 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1272 textclass.counters().step("bibitem");
1273 int number = textclass.counters().value("bibitem");
1274 if (par->bibitem()) {
1275 par->bibitem()->setCounter(number);
1276 par->params().labelString(layout->labelstring());
1278 // In biblio should't be following counters but...
1280 string s = layout->labelstring();
1282 // the caption hack:
1283 if (layout->labeltype == LABEL_SENSITIVE) {
1284 Paragraph * tmppar = par;
1287 while (tmppar && tmppar->inInset()
1288 // the single '=' is intended below
1289 && (in = tmppar->inInset()->owner())) {
1290 if (in->lyxCode() == Inset::FLOAT_CODE ||
1291 in->lyxCode() == Inset::WRAP_CODE) {
1295 tmppar = in->parOwner();
1301 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1303 textclass.counters().step(fl.type());
1305 // Doesn't work... yet.
1306 #warning use boost.format
1307 #if USE_BOOST_FORMAT
1308 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1309 // s << boost::format(_("%1$s %1$d:")
1311 // % buf->counters().value(fl.name());
1314 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1315 o << fl.name() << " #:";
1316 s = STRCONV(o.str());
1319 // par->SetLayout(0);
1320 // s = layout->labelstring;
1321 s = _("Senseless: ");
1324 par->params().labelString(s);
1326 // reset the enumeration counter. They are always reset
1327 // when there is any other layout between
1328 // Just fall-through between the cases so that all
1329 // enum counters deeper than enumdepth is also reset.
1330 switch (par->enumdepth) {
1332 textclass.counters().reset("enumi");
1334 textclass.counters().reset("enumii");
1336 textclass.counters().reset("enumiii");
1338 textclass.counters().reset("enumiv");
1344 // Updates all counters. Paragraphs with changed label string will be rebroken
1345 void LyXText::updateCounters(BufferView * bview) const
1347 Row * row = firstrow;
1348 Paragraph * par = row->par();
1350 // CHECK if this is really needed. (Lgb)
1351 bview->buffer()->params.getLyXTextClass().counters().reset();
1354 while (row->par() != par)
1357 string const oldLabel = par->params().labelString();
1359 // setCounter can potentially change the labelString.
1360 setCounter(bview->buffer(), par);
1362 string const & newLabel = par->params().labelString();
1364 if (oldLabel.empty() && !newLabel.empty()) {
1365 removeParagraph(row);
1366 appendParagraph(bview, row);
1374 void LyXText::insertInset(BufferView * bview, Inset * inset)
1376 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1378 setUndo(bview, Undo::FINISH, cursor.par(), cursor.par()->next());
1380 cursor.par()->insertInset(cursor.pos(), inset);
1381 // Just to rebreak and refresh correctly.
1382 // The character will not be inserted a second time
1383 insertChar(bview, Paragraph::META_INSET);
1384 // If we enter a highly editable inset the cursor should be to before
1385 // the inset. This couldn't happen before as Undo was not handled inside
1386 // inset now after the Undo LyX tries to call inset->Edit(...) again
1387 // and cannot do this as the cursor is behind the inset and GetInset
1388 // does not return the inset!
1389 if (isHighlyEditableInset(inset)) {
1390 cursorLeft(bview, true);
1396 void LyXText::copyEnvironmentType()
1398 copylayouttype = cursor.par()->layout()->name();
1402 void LyXText::pasteEnvironmentType(BufferView * bview)
1404 // do nothing if there has been no previous copyEnvironmentType()
1405 if (!copylayouttype.empty())
1406 setLayout(bview, copylayouttype);
1410 void LyXText::cutSelection(BufferView * bview, bool doclear, bool realcut)
1412 // Stuff what we got on the clipboard. Even if there is no selection.
1414 // There is a problem with having the stuffing here in that the
1415 // larger the selection the slower LyX will get. This can be
1416 // solved by running the line below only when the selection has
1417 // finished. The solution used currently just works, to make it
1418 // faster we need to be more clever and probably also have more
1419 // calls to stuffClipboard. (Lgb)
1420 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1422 // This doesn't make sense, if there is no selection
1423 if (!selection.set())
1426 // OK, we have a selection. This is always between selection.start
1427 // and selection.end
1429 // make sure that the depth behind the selection are restored, too
1430 Paragraph * endpar = selection.end.par()->next();
1431 Paragraph * undoendpar = endpar;
1433 if (endpar && endpar->getDepth()) {
1434 while (endpar && endpar->getDepth()) {
1435 endpar = endpar->next();
1436 undoendpar = endpar;
1438 } else if (endpar) {
1439 endpar = endpar->next(); // because of parindents etc.
1442 setUndo(bview, Undo::DELETE,
1443 selection.start.par(), undoendpar);
1445 // there are two cases: cut only within one paragraph or
1446 // more than one paragraph
1447 if (selection.start.par() == selection.end.par()) {
1448 // only within one paragraph
1449 endpar = selection.end.par();
1450 int pos = selection.end.pos();
1451 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1452 selection.start.pos(), pos,
1453 bview->buffer()->params.textclass,
1455 selection.end.pos(pos);
1457 endpar = selection.end.par();
1458 int pos = selection.end.pos();
1459 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1460 selection.start.pos(), pos,
1461 bview->buffer()->params.textclass,
1464 selection.end.par(endpar);
1465 selection.end.pos(pos);
1466 cursor.pos(selection.end.pos());
1468 endpar = endpar->next();
1470 // sometimes necessary
1472 selection.start.par()->stripLeadingSpaces();
1474 redoParagraphs(bview, selection.start, endpar);
1476 // cutSelection can invalidate the cursor so we need to set
1478 // we prefer the end for when tracking changes
1479 cursor = selection.end;
1481 // need a valid cursor. (Lgb)
1484 setCursor(bview, cursor.par(), cursor.pos());
1485 selection.cursor = cursor;
1486 updateCounters(bview);
1490 void LyXText::copySelection(BufferView * bview)
1492 // stuff the selection onto the X clipboard, from an explicit copy request
1493 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1495 // this doesnt make sense, if there is no selection
1496 if (!selection.set())
1499 // ok we have a selection. This is always between selection.start
1500 // and sel_end cursor
1502 // copy behind a space if there is one
1503 while (selection.start.par()->size() > selection.start.pos()
1504 && selection.start.par()->isLineSeparator(selection.start.pos())
1505 && (selection.start.par() != selection.end.par()
1506 || selection.start.pos() < selection.end.pos()))
1507 selection.start.pos(selection.start.pos() + 1);
1509 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1510 selection.start.pos(), selection.end.pos(),
1511 bview->buffer()->params.textclass);
1515 void LyXText::pasteSelection(BufferView * bview)
1517 // this does not make sense, if there is nothing to paste
1518 if (!CutAndPaste::checkPastePossible())
1521 setUndo(bview, Undo::INSERT,
1522 cursor.par(), cursor.par()->next());
1525 Paragraph * actpar = cursor.par();
1526 int pos = cursor.pos();
1528 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1529 bview->buffer()->params.textclass);
1531 redoParagraphs(bview, cursor, endpar);
1533 setCursor(bview, cursor.par(), cursor.pos());
1536 selection.cursor = cursor;
1537 setCursor(bview, actpar, pos);
1538 setSelection(bview);
1539 updateCounters(bview);
1543 void LyXText::setSelectionRange(BufferView * bview, lyx::pos_type length)
1548 selection.cursor = cursor;
1551 setSelection(bview);
1555 // simple replacing. The font of the first selected character is used
1556 void LyXText::replaceSelectionWithString(BufferView * bview,
1559 setCursorParUndo(bview);
1562 if (!selection.set()) { // create a dummy selection
1563 selection.end = cursor;
1564 selection.start = cursor;
1567 // Get font setting before we cut
1568 pos_type pos = selection.end.pos();
1569 LyXFont const font = selection.start.par()
1570 ->getFontSettings(bview->buffer()->params,
1571 selection.start.pos());
1573 // Insert the new string
1574 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1575 selection.end.par()->insertChar(pos, (*cit), font);
1579 // Cut the selection
1580 cutSelection(bview, true, false);
1586 // needed to insert the selection
1587 void LyXText::insertStringAsLines(BufferView * bview, string const & str)
1589 Paragraph * par = cursor.par();
1590 pos_type pos = cursor.pos();
1591 Paragraph * endpar = cursor.par()->next();
1593 setCursorParUndo(bview);
1595 // only to be sure, should not be neccessary
1598 bview->buffer()->insertStringAsLines(par, pos, current_font, str);
1600 redoParagraphs(bview, cursor, endpar);
1601 setCursor(bview, cursor.par(), cursor.pos());
1602 selection.cursor = cursor;
1603 setCursor(bview, par, pos);
1604 setSelection(bview);
1608 // turns double-CR to single CR, others where converted into one
1609 // blank. Then InsertStringAsLines is called
1610 void LyXText::insertStringAsParagraphs(BufferView * bview, string const & str)
1612 string linestr(str);
1613 bool newline_inserted = false;
1614 for (string::size_type i = 0; i < linestr.length(); ++i) {
1615 if (linestr[i] == '\n') {
1616 if (newline_inserted) {
1617 // we know that \r will be ignored by
1618 // InsertStringA. Of course, it is a dirty
1619 // trick, but it works...
1620 linestr[i - 1] = '\r';
1624 newline_inserted = true;
1626 } else if (IsPrintable(linestr[i])) {
1627 newline_inserted = false;
1630 insertStringAsLines(bview, linestr);
1634 void LyXText::checkParagraph(BufferView * bview, Paragraph * par,
1637 LyXCursor tmpcursor;
1641 Row * row = getRow(par, pos, y);
1643 // is there a break one row above
1644 if (row->previous() && row->previous()->par() == row->par()) {
1645 z = rowBreakPoint(*bview, *row->previous());
1646 if (z >= row->pos()) {
1647 // set the dimensions of the row above
1648 y -= row->previous()->height();
1650 refresh_row = row->previous();
1651 status(bview, LyXText::NEED_MORE_REFRESH);
1653 breakAgain(bview, row->previous());
1655 // set the cursor again. Otherwise
1656 // dangling pointers are possible
1657 setCursor(bview, cursor.par(), cursor.pos(),
1658 false, cursor.boundary());
1659 selection.cursor = cursor;
1664 int const tmpheight = row->height();
1665 pos_type const tmplast = row->lastPos();
1669 breakAgain(bview, row);
1670 if (row->height() == tmpheight && row->lastPos() == tmplast)
1671 status(bview, LyXText::NEED_VERY_LITTLE_REFRESH);
1673 status(bview, LyXText::NEED_MORE_REFRESH);
1675 // check the special right address boxes
1676 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1683 redoDrawingOfParagraph(bview, tmpcursor);
1686 // set the cursor again. Otherwise dangling pointers are possible
1687 // also set the selection
1689 if (selection.set()) {
1691 setCursorIntern(bview, selection.cursor.par(), selection.cursor.pos(),
1692 false, selection.cursor.boundary());
1693 selection.cursor = cursor;
1694 setCursorIntern(bview, selection.start.par(),
1695 selection.start.pos(),
1696 false, selection.start.boundary());
1697 selection.start = cursor;
1698 setCursorIntern(bview, selection.end.par(),
1699 selection.end.pos(),
1700 false, selection.end.boundary());
1701 selection.end = cursor;
1702 setCursorIntern(bview, last_sel_cursor.par(),
1703 last_sel_cursor.pos(),
1704 false, last_sel_cursor.boundary());
1705 last_sel_cursor = cursor;
1708 setCursorIntern(bview, cursor.par(), cursor.pos(),
1709 false, cursor.boundary());
1713 // returns false if inset wasn't found
1714 bool LyXText::updateInset(BufferView * bview, Inset * inset)
1716 // first check the current paragraph
1717 int pos = cursor.par()->getPositionOfInset(inset);
1719 checkParagraph(bview, cursor.par(), pos);
1723 // check every paragraph
1725 Paragraph * par = ownerParagraph();
1727 pos = par->getPositionOfInset(inset);
1729 checkParagraph(bview, par, pos);
1739 bool LyXText::setCursor(BufferView * bview, Paragraph * par,
1741 bool setfont, bool boundary) const
1743 LyXCursor old_cursor = cursor;
1744 setCursorIntern(bview, par, pos, setfont, boundary);
1745 return deleteEmptyParagraphMechanism(bview, old_cursor);
1749 void LyXText::setCursor(BufferView * bview, LyXCursor & cur, Paragraph * par,
1750 pos_type pos, bool boundary) const
1757 cur.boundary(boundary);
1759 // get the cursor y position in text
1761 Row * row = getRow(par, pos, y);
1762 Row * old_row = row;
1764 // if we are before the first char of this row and are still in the
1765 // same paragraph and there is a previous row then put the cursor on
1766 // the end of the previous row
1767 cur.iy(y + row->baseline());
1769 if (row->previous() && pos &&
1770 row->previous()->par() == row->par() &&
1771 pos < par->size() &&
1772 par->getChar(pos) == Paragraph::META_INSET &&
1773 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1775 row = row->previous();
1780 // y is now the beginning of the cursor row
1781 y += row->baseline();
1782 // y is now the cursor baseline
1785 pos_type last = old_row->lastPrintablePos();
1787 // None of these should happen, but we're scaredy-cats
1788 if (pos > par->size()) {
1789 lyxerr << "dont like 1 please report" << endl;
1792 } else if (pos > last + 1) {
1793 lyxerr << "dont like 2 please report" << endl;
1794 // This shouldn't happen.
1797 } else if (pos < row->pos()) {
1798 lyxerr << "dont like 3 please report" << endl;
1803 // now get the cursors x position
1804 float x = getCursorX(bview, row, pos, last, boundary);
1807 if (old_row != row) {
1808 x = getCursorX(bview, old_row, pos, last, boundary);
1815 float LyXText::getCursorX(BufferView * bview, Row * row,
1816 pos_type pos, pos_type last, bool boundary) const
1818 pos_type cursor_vpos = 0;
1820 float fill_separator;
1822 float fill_label_hfill;
1823 // This call HAS to be here because of the BidiTables!!!
1824 prepareToPrint(bview, row, x, fill_separator, fill_hfill,
1827 if (last < row->pos())
1828 cursor_vpos = row->pos();
1829 else if (pos > last && !boundary)
1830 cursor_vpos = (row->par()->isRightToLeftPar(bview->buffer()->params))
1831 ? row->pos() : last + 1;
1832 else if (pos > row->pos() &&
1833 (pos > last || boundary))
1834 /// Place cursor after char at (logical) position pos - 1
1835 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1836 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1838 /// Place cursor before char at (logical) position pos
1839 cursor_vpos = (bidi_level(pos) % 2 == 0)
1840 ? log2vis(pos) : log2vis(pos) + 1;
1842 pos_type body_pos = row->par()->beginningOfBody();
1843 if ((body_pos > 0) &&
1844 ((body_pos-1 > last) ||
1845 !row->par()->isLineSeparator(body_pos-1)))
1848 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1849 pos_type pos = vis2log(vpos);
1850 if (body_pos > 0 && pos == body_pos - 1) {
1851 x += fill_label_hfill +
1852 font_metrics::width(
1853 row->par()->layout()->labelsep,
1854 getLabelFont(bview->buffer(),
1856 if (row->par()->isLineSeparator(body_pos - 1))
1857 x -= singleWidth(bview,
1858 row->par(), body_pos - 1);
1860 if (row->hfillExpansion(pos)) {
1861 x += singleWidth(bview, row->par(), pos);
1862 if (pos >= body_pos)
1865 x += fill_label_hfill;
1866 } else if (row->par()->isSeparator(pos)) {
1867 x += singleWidth(bview, row->par(), pos);
1868 if (pos >= body_pos)
1869 x += fill_separator;
1871 x += singleWidth(bview, row->par(), pos);
1877 void LyXText::setCursorIntern(BufferView * bview, Paragraph * par,
1878 pos_type pos, bool setfont, bool boundary) const
1880 InsetText * it = static_cast<InsetText *>(par->inInset());
1882 if (it != inset_owner) {
1883 lyxerr[Debug::INSETS] << "InsetText is " << it
1885 << "inset_owner is "
1886 << inset_owner << endl;
1887 #ifdef WITH_WARNINGS
1888 #warning I believe this code is wrong. (Lgb)
1889 #warning Jürgen, have a look at this. (Lgb)
1890 #warning Hmmm, I guess you are right but we
1891 #warning should verify when this is needed
1893 // Jürgen, would you like to have a look?
1894 // I guess we need to move the outer cursor
1895 // and open and lock the inset (bla bla bla)
1896 // stuff I don't know... so can you have a look?
1898 // I moved the lyxerr stuff in here so we can see if
1899 // this is actually really needed and where!
1901 // it->getLyXText(bview)->setCursorIntern(bview, par, pos, setfont, boundary);
1906 setCursor(bview, cursor, par, pos, boundary);
1908 setCurrentFont(bview);
1912 void LyXText::setCurrentFont(BufferView * bview) const
1914 pos_type pos = cursor.pos();
1915 if (cursor.boundary() && pos > 0)
1919 if (pos == cursor.par()->size())
1921 else // potentional bug... BUG (Lgb)
1922 if (cursor.par()->isSeparator(pos)) {
1923 if (pos > cursor.row()->pos() &&
1924 bidi_level(pos) % 2 ==
1925 bidi_level(pos - 1) % 2)
1927 else if (pos + 1 < cursor.par()->size())
1933 cursor.par()->getFontSettings(bview->buffer()->params, pos);
1934 real_current_font = getFont(bview->buffer(), cursor.par(), pos);
1936 if (cursor.pos() == cursor.par()->size() &&
1937 isBoundary(bview->buffer(), cursor.par(), cursor.pos()) &&
1938 !cursor.boundary()) {
1939 Language const * lang =
1940 cursor.par()->getParLanguage(bview->buffer()->params);
1941 current_font.setLanguage(lang);
1942 current_font.setNumber(LyXFont::OFF);
1943 real_current_font.setLanguage(lang);
1944 real_current_font.setNumber(LyXFont::OFF);
1949 // returns the column near the specified x-coordinate of the row
1950 // x is set to the real beginning of this column
1952 LyXText::getColumnNearX(BufferView * bview, Row * row, int & x,
1953 bool & boundary) const
1956 float fill_separator;
1958 float fill_label_hfill;
1960 prepareToPrint(bview, row, tmpx, fill_separator,
1961 fill_hfill, fill_label_hfill);
1963 pos_type vc = row->pos();
1964 pos_type last = row->lastPrintablePos();
1967 LyXLayout_ptr const & layout = row->par()->layout();
1969 bool left_side = false;
1971 pos_type body_pos = row->par()->beginningOfBody();
1972 float last_tmpx = tmpx;
1975 (body_pos - 1 > last ||
1976 !row->par()->isLineSeparator(body_pos - 1)))
1979 // check for empty row
1980 if (!row->par()->size()) {
1985 while (vc <= last && tmpx <= x) {
1988 if (body_pos > 0 && c == body_pos-1) {
1989 tmpx += fill_label_hfill +
1990 font_metrics::width(layout->labelsep,
1991 getLabelFont(bview->buffer(), row->par()));
1992 if (row->par()->isLineSeparator(body_pos - 1))
1993 tmpx -= singleWidth(bview, row->par(), body_pos-1);
1996 if (row->hfillExpansion(c)) {
1997 tmpx += singleWidth(bview, row->par(), c);
2001 tmpx += fill_label_hfill;
2002 } else if (row->par()->isSeparator(c)) {
2003 tmpx += singleWidth(bview, row->par(), c);
2005 tmpx+= fill_separator;
2007 tmpx += singleWidth(bview, row->par(), c);
2012 if ((tmpx + last_tmpx) / 2 > x) {
2017 if (vc > last + 1) // This shouldn't happen.
2021 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
2022 // some speedup if rtl_support=false
2023 && (!row->next() || row->next()->par() != row->par());
2024 bool const rtl = (lastrow)
2025 ? row->par()->isRightToLeftPar(bview->buffer()->params)
2026 : false; // If lastrow is false, we don't need to compute
2027 // the value of rtl.
2030 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
2031 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
2033 else if (vc == row->pos()) {
2035 if (bidi_level(c) % 2 == 1)
2038 c = vis2log(vc - 1);
2039 bool const rtl = (bidi_level(c) % 2 == 1);
2040 if (left_side == rtl) {
2042 boundary = isBoundary(bview->buffer(), row->par(), c);
2046 if (row->pos() <= last && c > last
2047 && row->par()->isNewline(last)) {
2048 if (bidi_level(last) % 2 == 0)
2049 tmpx -= singleWidth(bview, row->par(), last);
2051 tmpx += singleWidth(bview, row->par(), last);
2061 void LyXText::setCursorFromCoordinates(BufferView * bview, int x, int y) const
2063 LyXCursor old_cursor = cursor;
2065 setCursorFromCoordinates(bview, cursor, x, y);
2066 setCurrentFont(bview);
2067 deleteEmptyParagraphMechanism(bview, old_cursor);
2074 * return true if the cursor given is at the end of a row,
2075 * and the next row is filled by an inset that spans an entire
2078 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2081 Row const & next = *row.next();
2083 if (next.pos() != cur.pos() || next.par() != cur.par())
2085 if (!cur.par()->isInset(cur.pos()))
2087 Inset const * inset = cur.par()->getInset(cur.pos());
2088 if (inset->needFullRow() || inset->display())
2095 void LyXText::setCursorFromCoordinates(BufferView * bview, LyXCursor & cur,
2098 // Get the row first.
2100 Row * row = getRowNearY(y);
2102 pos_type const column = getColumnNearX(bview, row, x, bound);
2103 cur.par(row->par());
2104 cur.pos(row->pos() + column);
2106 cur.y(y + row->baseline());
2109 if (beforeFullRowInset(*row, cur)) {
2110 pos_type last = row->lastPrintablePos();
2111 float x = getCursorX(bview, row->next(), cur.pos(), last, bound);
2113 cur.iy(y + row->height() + row->next()->baseline());
2114 cur.irow(row->next());
2120 cur.boundary(bound);
2124 void LyXText::cursorLeft(BufferView * bview, bool internal) const
2126 if (cursor.pos() > 0) {
2127 bool boundary = cursor.boundary();
2128 setCursor(bview, cursor.par(), cursor.pos() - 1, true, false);
2129 if (!internal && !boundary &&
2130 isBoundary(bview->buffer(), cursor.par(), cursor.pos() + 1))
2131 setCursor(bview, cursor.par(), cursor.pos() + 1, true, true);
2132 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2133 Paragraph * par = cursor.par()->previous();
2134 setCursor(bview, par, par->size());
2139 void LyXText::cursorRight(BufferView * bview, bool internal) const
2141 if (!internal && cursor.boundary() &&
2142 !cursor.par()->isNewline(cursor.pos()))
2143 setCursor(bview, cursor.par(), cursor.pos(), true, false);
2144 else if (cursor.pos() < cursor.par()->size()) {
2145 setCursor(bview, cursor.par(), cursor.pos() + 1, true, false);
2147 isBoundary(bview->buffer(), cursor.par(), cursor.pos()))
2148 setCursor(bview, cursor.par(), cursor.pos(), true, true);
2149 } else if (cursor.par()->next())
2150 setCursor(bview, cursor.par()->next(), 0);
2154 void LyXText::cursorUp(BufferView * bview, bool selecting) const
2157 int x = cursor.x_fix();
2158 int y = cursor.y() - cursor.row()->baseline() - 1;
2159 setCursorFromCoordinates(bview, x, y);
2162 int y1 = cursor.iy() - topy;
2165 Inset * inset_hit = checkInsetHit(bview, x, y1);
2166 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2167 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2171 setCursorFromCoordinates(bview, cursor.x_fix(),
2172 cursor.y() - cursor.row()->baseline() - 1);
2177 void LyXText::cursorDown(BufferView * bview, bool selecting) const
2180 int x = cursor.x_fix();
2181 int y = cursor.y() - cursor.row()->baseline() +
2182 cursor.row()->height() + 1;
2183 setCursorFromCoordinates(bview, x, y);
2184 if (!selecting && cursor.row() == cursor.irow()) {
2186 int y1 = cursor.iy() - topy;
2189 Inset * inset_hit = checkInsetHit(bview, x, y1);
2190 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2191 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2195 setCursorFromCoordinates(bview, cursor.x_fix(),
2196 cursor.y() - cursor.row()->baseline()
2197 + cursor.row()->height() + 1);
2202 void LyXText::cursorUpParagraph(BufferView * bview) const
2204 if (cursor.pos() > 0) {
2205 setCursor(bview, cursor.par(), 0);
2207 else if (cursor.par()->previous()) {
2208 setCursor(bview, cursor.par()->previous(), 0);
2213 void LyXText::cursorDownParagraph(BufferView * bview) const
2215 if (cursor.par()->next()) {
2216 setCursor(bview, cursor.par()->next(), 0);
2218 setCursor(bview, cursor.par(), cursor.par()->size());
2222 // fix the cursor `cur' after a characters has been deleted at `where'
2223 // position. Called by deleteEmptyParagraphMechanism
2224 void LyXText::fixCursorAfterDelete(BufferView * bview,
2226 LyXCursor const & where) const
2228 // if cursor is not in the paragraph where the delete occured,
2230 if (cur.par() != where.par())
2233 // if cursor position is after the place where the delete occured,
2235 if (cur.pos() > where.pos())
2236 cur.pos(cur.pos()-1);
2238 // check also if we don't want to set the cursor on a spot behind the
2239 // pagragraph because we erased the last character.
2240 if (cur.pos() > cur.par()->size())
2241 cur.pos(cur.par()->size());
2243 // recompute row et al. for this cursor
2244 setCursor(bview, cur, cur.par(), cur.pos(), cur.boundary());
2248 bool LyXText::deleteEmptyParagraphMechanism(BufferView * bview,
2249 LyXCursor const & old_cursor) const
2251 // Would be wrong to delete anything if we have a selection.
2252 if (selection.set())
2255 // We allow all kinds of "mumbo-jumbo" when freespacing.
2256 if (old_cursor.par()->layout()->free_spacing
2257 || old_cursor.par()->isFreeSpacing()) {
2261 /* Ok I'll put some comments here about what is missing.
2262 I have fixed BackSpace (and thus Delete) to not delete
2263 double-spaces automagically. I have also changed Cut,
2264 Copy and Paste to hopefully do some sensible things.
2265 There are still some small problems that can lead to
2266 double spaces stored in the document file or space at
2267 the beginning of paragraphs. This happens if you have
2268 the cursor betwenn to spaces and then save. Or if you
2269 cut and paste and the selection have a space at the
2270 beginning and then save right after the paste. I am
2271 sure none of these are very hard to fix, but I will
2272 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2273 that I can get some feedback. (Lgb)
2276 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2277 // delete the LineSeparator.
2280 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2281 // delete the LineSeparator.
2284 // If the pos around the old_cursor were spaces, delete one of them.
2285 if (old_cursor.par() != cursor.par()
2286 || old_cursor.pos() != cursor.pos()) {
2287 // Only if the cursor has really moved
2289 if (old_cursor.pos() > 0
2290 && old_cursor.pos() < old_cursor.par()->size()
2291 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2292 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2293 old_cursor.par()->erase(old_cursor.pos() - 1);
2294 redoParagraphs(bview, old_cursor, old_cursor.par()->next());
2296 #ifdef WITH_WARNINGS
2297 #warning This will not work anymore when we have multiple views of the same buffer
2298 // In this case, we will have to correct also the cursors held by
2299 // other bufferviews. It will probably be easier to do that in a more
2300 // automated way in LyXCursor code. (JMarc 26/09/2001)
2302 // correct all cursors held by the LyXText
2303 fixCursorAfterDelete(bview, cursor, old_cursor);
2304 fixCursorAfterDelete(bview, selection.cursor,
2306 fixCursorAfterDelete(bview, selection.start,
2308 fixCursorAfterDelete(bview, selection.end, old_cursor);
2309 fixCursorAfterDelete(bview, last_sel_cursor,
2311 fixCursorAfterDelete(bview, toggle_cursor, old_cursor);
2312 fixCursorAfterDelete(bview, toggle_end_cursor,
2318 // don't delete anything if this is the ONLY paragraph!
2319 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2322 // Do not delete empty paragraphs with keepempty set.
2323 if (old_cursor.par()->layout()->keepempty)
2326 // only do our magic if we changed paragraph
2327 if (old_cursor.par() == cursor.par())
2330 // record if we have deleted a paragraph
2331 // we can't possibly have deleted a paragraph before this point
2332 bool deleted = false;
2334 if ((old_cursor.par()->empty()
2335 || (old_cursor.par()->size() == 1
2336 && old_cursor.par()->isLineSeparator(0)))) {
2337 // ok, we will delete anything
2338 LyXCursor tmpcursor;
2340 // make sure that you do not delete any environments
2341 status(bview, LyXText::NEED_MORE_REFRESH);
2344 if (old_cursor.row()->previous()) {
2345 refresh_row = old_cursor.row()->previous();
2346 refresh_y = old_cursor.y() - old_cursor.row()->baseline() - refresh_row->height();
2348 cursor = old_cursor; // that undo can restore the right cursor position
2349 Paragraph * endpar = old_cursor.par()->next();
2350 if (endpar && endpar->getDepth()) {
2351 while (endpar && endpar->getDepth()) {
2352 endpar = endpar->next();
2355 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2359 removeRow(old_cursor.row());
2360 if (ownerParagraph() == old_cursor.par()) {
2361 ownerParagraph(ownerParagraph()->next());
2364 delete old_cursor.par();
2366 /* Breakagain the next par. Needed because of
2367 * the parindent that can occur or dissappear.
2368 * The next row can change its height, if
2369 * there is another layout before */
2370 if (refresh_row->next()) {
2371 breakAgain(bview, refresh_row->next());
2372 updateCounters(bview);
2374 setHeightOfRow(bview, refresh_row);
2376 refresh_row = old_cursor.row()->next();
2377 refresh_y = old_cursor.y() - old_cursor.row()->baseline();
2380 cursor = old_cursor; // that undo can restore the right cursor position
2381 Paragraph * endpar = old_cursor.par()->next();
2382 if (endpar && endpar->getDepth()) {
2383 while (endpar && endpar->getDepth()) {
2384 endpar = endpar->next();
2387 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2391 removeRow(old_cursor.row());
2393 if (ownerParagraph() == old_cursor.par()) {
2394 ownerParagraph(ownerParagraph()->next());
2397 delete old_cursor.par();
2399 /* Breakagain the next par. Needed because of
2400 the parindent that can occur or dissappear.
2401 The next row can change its height, if
2402 there is another layout before */
2404 breakAgain(bview, refresh_row);
2405 updateCounters(bview);
2410 setCursorIntern(bview, cursor.par(), cursor.pos());
2412 if (selection.cursor.par() == old_cursor.par()
2413 && selection.cursor.pos() == old_cursor.pos()) {
2414 // correct selection
2415 selection.cursor = cursor;
2419 if (old_cursor.par()->stripLeadingSpaces()) {
2420 redoParagraphs(bview, old_cursor,
2421 old_cursor.par()->next());
2423 setCursorIntern(bview, cursor.par(), cursor.pos());
2424 selection.cursor = cursor;
2431 Paragraph * LyXText::ownerParagraph() const
2434 return inset_owner->paragraph();
2436 return &*(bv_owner->buffer()->paragraphs.begin());
2440 void LyXText::ownerParagraph(Paragraph * p) const
2443 inset_owner->paragraph(p);
2445 bv_owner->buffer()->paragraphs.set(p);
2450 void LyXText::ownerParagraph(int id, Paragraph * p) const
2452 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2453 if (op && op->inInset()) {
2454 static_cast<InsetText *>(op->inInset())->paragraph(p);
2461 LyXText::text_status LyXText::status() const
2467 void LyXText::status(BufferView * bview, LyXText::text_status new_status) const
2469 // We should not lose information from previous status
2470 // sets, or we'll forget to repaint the other bits
2471 // covered by the NEED_MORE_REFRESH
2472 if (new_status == NEED_VERY_LITTLE_REFRESH
2473 && status_ == NEED_MORE_REFRESH)
2476 status_ = new_status;
2478 if (new_status == UNCHANGED)
2484 LyXText * t = bview->text;
2486 // We are an inset's lyxtext. Tell the top-level lyxtext
2487 // it needs to update the row we're in.
2488 t->status(bview, NEED_VERY_LITTLE_REFRESH);
2489 if (!t->refresh_row) {
2490 t->refresh_row = t->cursor.row();
2491 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2496 void LyXText::clearPaint()
2498 status_ = UNCHANGED;
2504 void LyXText::postChangedInDraw()
2506 status_ = CHANGED_IN_DRAW;
2510 void LyXText::postPaint(BufferView & bv, int start_y)
2512 status_ = NEED_MORE_REFRESH;
2513 refresh_y = start_y;
2519 // We are an inset's lyxtext. Tell the top-level lyxtext
2520 // it needs to update the row we're in.
2522 LyXText * t = bv.text;
2524 // FIXME: but what if this row is below ?
2525 if (!t->refresh_row) {
2526 t->refresh_row = t->cursor.row();
2527 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2532 // FIXME: we should probably remove this y parameter,
2533 // make refresh_y be 0, and use row->y etc.
2534 void LyXText::postRowPaint(BufferView & bv, Row * row, int start_y)
2536 // FIXME: shouldn't this check that we're not updating
2537 // above the existing refresh_y ??
2538 if (status_ == NEED_MORE_REFRESH)
2541 status_ = NEED_VERY_LITTLE_REFRESH;
2542 refresh_y = start_y;
2548 // We are an inset's lyxtext. Tell the top-level lyxtext
2549 // it needs to update the row we're in.
2551 LyXText * t = bv.text;
2553 // FIXME: but what if this new row is above ?
2554 // Why the !t->refresh_row at all ?
2555 if (!t->refresh_row) {
2556 t->refresh_row = t->cursor.row();
2557 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2562 bool LyXText::isTopLevel() const
2564 /// only the top-level lyxtext has a non-null bv owner
2569 bool LyXText::isInInset() const
2575 int defaultRowHeight()
2577 LyXFont const font(LyXFont::ALL_SANE);
2578 return int(font_metrics::maxAscent(font)
2579 + font_metrics::maxDescent(font) * 1.5);