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();
85 copylayouttype.erase();
87 status_ = LyXText::UNCHANGED;
91 Paragraph * par = ownerParagraph();
92 current_font = getFont(bview->buffer(), par, 0);
95 insertParagraph(bview, par, lastrow);
98 setCursorIntern(bview, firstrow->par(), 0);
99 selection.cursor = cursor;
101 updateCounters(bview);
107 // Delete all rows, this does not touch the paragraphs!
108 Row * tmprow = firstrow;
110 tmprow = firstrow->next();
119 LyXFont const realizeFont(LyXFont const & font,
123 LyXTextClass const & tclass = buf->params.getLyXTextClass();
124 LyXFont tmpfont(font);
125 Paragraph::depth_type par_depth = par->getDepth();
127 // Resolve against environment font information
128 while (par && par_depth && !tmpfont.resolved()) {
129 par = par->outerHook();
131 tmpfont.realize(par->layout()->font);
132 par_depth = par->getDepth();
136 tmpfont.realize(tclass.defaultfont());
144 // Gets the fully instantiated font at a given position in a paragraph
145 // Basically the same routine as Paragraph::getFont() in paragraph.C.
146 // The difference is that this one is used for displaying, and thus we
147 // are allowed to make cosmetic improvements. For instance make footnotes
149 // If position is -1, we get the layout font of the paragraph.
150 // If position is -2, we get the font of the manual label of the paragraph.
151 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
154 lyx::Assert(pos >= 0);
156 LyXLayout_ptr const & layout = par->layout();
158 // We specialize the 95% common case:
159 if (!par->getDepth()) {
160 if (layout->labeltype == LABEL_MANUAL
161 && pos < par->beginningOfBody()) {
163 LyXFont f = par->getFontSettings(buf->params, pos);
165 par->inInset()->getDrawFont(f);
166 return f.realize(layout->reslabelfont);
168 LyXFont f = par->getFontSettings(buf->params, pos);
170 par->inInset()->getDrawFont(f);
171 return f.realize(layout->resfont);
175 // The uncommon case need not be optimized as much
179 if (pos < par->beginningOfBody()) {
181 layoutfont = layout->labelfont;
184 layoutfont = layout->font;
187 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
188 tmpfont.realize(layoutfont);
191 par->inInset()->getDrawFont(tmpfont);
193 return realizeFont(tmpfont, buf, par);
197 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
199 LyXLayout_ptr const & layout = par->layout();
201 if (!par->getDepth()) {
202 return layout->resfont;
205 return realizeFont(layout->font, buf, par);
209 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
211 LyXLayout_ptr const & layout = par->layout();
213 if (!par->getDepth()) {
214 return layout->reslabelfont;
217 return realizeFont(layout->labelfont, buf, par);
221 void LyXText::setCharFont(BufferView * bv, Paragraph * par,
222 pos_type pos, LyXFont const & fnt,
225 Buffer const * buf = bv->buffer();
226 LyXFont font = getFont(buf, par, pos);
227 font.update(fnt, buf->params.language, toggleall);
228 // Let the insets convert their font
229 if (par->isInset(pos)) {
230 Inset * inset = par->getInset(pos);
231 if (isEditableInset(inset)) {
232 UpdatableInset * uinset =
233 static_cast<UpdatableInset *>(inset);
234 uinset->setFont(bv, fnt, toggleall, true);
238 // Plug thru to version below:
239 setCharFont(buf, par, pos, font);
243 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
244 pos_type pos, LyXFont const & fnt)
248 LyXTextClass const & tclass = buf->params.getLyXTextClass();
249 LyXLayout_ptr const & layout = par->layout();
251 // Get concrete layout font to reduce against
254 if (pos < par->beginningOfBody())
255 layoutfont = layout->labelfont;
257 layoutfont = layout->font;
259 // Realize against environment font information
260 if (par->getDepth()) {
261 Paragraph * tp = par;
262 while (!layoutfont.resolved() && tp && tp->getDepth()) {
263 tp = tp->outerHook();
265 layoutfont.realize(tp->layout()->font);
269 layoutfont.realize(tclass.defaultfont());
271 // Now, reduce font against full layout font
272 font.reduce(layoutfont);
274 par->setFont(pos, font);
278 // inserts a new row before the specified row, increments
279 // the touched counters
280 void LyXText::insertRow(Row * row, Paragraph * par,
283 Row * tmprow = new Row;
286 tmprow->next(firstrow);
289 tmprow->previous(row);
290 tmprow->next(row->next());
295 tmprow->next()->previous(tmprow);
297 if (tmprow->previous())
298 tmprow->previous()->next(tmprow);
309 // removes the row and reset the touched counters
310 void LyXText::removeRow(Row * row) const
312 Row * row_prev = row->previous();
314 row->next()->previous(row_prev);
316 firstrow = row->next();
317 // lyx::Assert(firstrow);
319 row_prev->next(row->next());
321 if (row == lastrow) {
322 lyx::Assert(!row->next());
325 if (refresh_row == row) {
326 refresh_row = row_prev ? row_prev : row->next();
327 // what about refresh_y, refresh_height
329 if (top_row_ == row) {
331 top_row_ = row->next();
332 top_row_offset_ -= row->height();
339 height -= row->height(); // the text becomes smaller
345 // remove all following rows of the paragraph of the specified row.
346 void LyXText::removeParagraph(Row * row) const
348 Paragraph * tmppar = row->par();
352 while (row && row->par() == tmppar) {
353 tmprow = row->next();
360 // insert the specified paragraph behind the specified row
361 void LyXText::insertParagraph(BufferView * bview, Paragraph * par,
364 // insert a new row, starting at position 0
365 insertRow(row, par, 0);
367 // and now append the whole paragraph before the new row
370 appendParagraph(bview, firstrow);
372 row->next()->height(0);
373 appendParagraph(bview, row->next());
378 Inset * LyXText::getInset() const
380 if (cursor.pos() < cursor.par()->size()
381 && cursor.par()->isInset(cursor.pos())) {
382 return cursor.par()->getInset(cursor.pos());
388 void LyXText::toggleInset(BufferView * bview)
390 Inset * inset = getInset();
391 // is there an editable inset at cursor position?
392 if (!isEditableInset(inset)) {
393 // No, try to see if we are inside a collapsable inset
394 if (inset_owner && inset_owner->owner()
395 && inset_owner->owner()->isOpen()) {
396 bview->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
397 inset_owner->owner()->close(bview);
398 bview->getLyXText()->cursorRight(bview);
402 //bview->owner()->message(inset->editMessage());
404 // do we want to keep this?? (JMarc)
405 if (!isHighlyEditableInset(inset))
406 setCursorParUndo(bview);
408 if (inset->isOpen()) {
414 inset->open(bview, !inset->isOpen());
419 /* used in setlayout */
420 // Asger is not sure we want to do this...
421 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
424 LyXLayout_ptr const & layout = par.layout();
427 for (pos_type pos = 0; pos < par.size(); ++pos) {
428 if (pos < par.beginningOfBody())
429 layoutfont = layout->labelfont;
431 layoutfont = layout->font;
433 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
434 tmpfont.reduce(layoutfont);
435 par.setFont(pos, tmpfont);
440 Paragraph * LyXText::setLayout(BufferView * bview,
441 LyXCursor & cur, LyXCursor & sstart_cur,
442 LyXCursor & send_cur,
443 string const & layout)
445 Paragraph * endpar = send_cur.par()->next();
446 Paragraph * undoendpar = endpar;
448 if (endpar && endpar->getDepth()) {
449 while (endpar && endpar->getDepth()) {
450 endpar = endpar->next();
454 endpar = endpar->next(); // because of parindents etc.
457 setUndo(bview, Undo::EDIT, sstart_cur.par(), undoendpar);
459 // ok we have a selection. This is always between sstart_cur
460 // and sel_end cursor
462 Paragraph * par = sstart_cur.par();
463 Paragraph * epar = send_cur.par()->next();
465 LyXLayout_ptr const & lyxlayout =
466 bview->buffer()->params.getLyXTextClass()[layout];
469 par->applyLayout(lyxlayout);
470 makeFontEntriesLayoutSpecific(*bview->buffer(), *par);
471 Paragraph * fppar = par;
472 fppar->params().spaceTop(lyxlayout->fill_top ?
473 VSpace(VSpace::VFILL)
474 : VSpace(VSpace::NONE));
475 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
476 VSpace(VSpace::VFILL)
477 : VSpace(VSpace::NONE));
478 if (lyxlayout->margintype == MARGIN_MANUAL)
479 par->setLabelWidthString(lyxlayout->labelstring());
482 } while (par != epar);
488 // set layout over selection and make a total rebreak of those paragraphs
489 void LyXText::setLayout(BufferView * bview, string const & layout)
491 LyXCursor tmpcursor = cursor; /* store the current cursor */
493 // if there is no selection just set the layout
494 // of the current paragraph */
495 if (!selection.set()) {
496 selection.start = cursor; // dummy selection
497 selection.end = cursor;
499 Paragraph * endpar = setLayout(bview, cursor, selection.start,
500 selection.end, layout);
501 redoParagraphs(bview, selection.start, endpar);
503 // we have to reset the selection, because the
504 // geometry could have changed
505 setCursor(bview, selection.start.par(),
506 selection.start.pos(), false);
507 selection.cursor = cursor;
508 setCursor(bview, selection.end.par(), selection.end.pos(), false);
509 updateCounters(bview);
512 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true);
516 // increment depth over selection and
517 // make a total rebreak of those paragraphs
518 void LyXText::incDepth(BufferView * bview)
520 // If there is no selection, just use the current paragraph
521 if (!selection.set()) {
522 selection.start = cursor; // dummy selection
523 selection.end = cursor;
526 // We end at the next paragraph with depth 0
527 Paragraph * endpar = selection.end.par()->next();
529 Paragraph * undoendpar = endpar;
531 if (endpar && endpar->getDepth()) {
532 while (endpar && endpar->getDepth()) {
533 endpar = endpar->next();
537 endpar = endpar->next(); // because of parindents etc.
540 setUndo(bview, Undo::EDIT,
541 selection.start.par(), undoendpar);
543 LyXCursor tmpcursor = cursor; // store the current cursor
545 // ok we have a selection. This is always between sel_start_cursor
546 // and sel_end cursor
547 cursor = selection.start;
550 // NOTE: you can't change the depth of a bibliography entry
551 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
552 Paragraph * prev = cursor.par()->previous();
555 if (cursor.par()->getDepth()
556 < prev->getMaxDepthAfter()) {
557 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
561 if (cursor.par() == selection.end.par())
563 cursor.par(cursor.par()->next());
566 redoParagraphs(bview, selection.start, endpar);
568 // we have to reset the selection, because the
569 // geometry could have changed
570 setCursor(bview, selection.start.par(), selection.start.pos());
571 selection.cursor = cursor;
572 setCursor(bview, selection.end.par(), selection.end.pos());
573 updateCounters(bview);
576 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
580 // decrement depth over selection and
581 // make a total rebreak of those paragraphs
582 void LyXText::decDepth(BufferView * bview)
584 // if there is no selection just set the layout
585 // of the current paragraph
586 if (!selection.set()) {
587 selection.start = cursor; // dummy selection
588 selection.end = cursor;
590 Paragraph * endpar = selection.end.par()->next();
591 Paragraph * undoendpar = endpar;
593 if (endpar && endpar->getDepth()) {
594 while (endpar && endpar->getDepth()) {
595 endpar = endpar->next();
599 endpar = endpar->next(); // because of parindents etc.
602 setUndo(bview, Undo::EDIT,
603 selection.start.par(), undoendpar);
605 LyXCursor tmpcursor = cursor; // store the current cursor
607 // ok we have a selection. This is always between sel_start_cursor
608 // and sel_end cursor
609 cursor = selection.start;
612 if (cursor.par()->params().depth()) {
613 cursor.par()->params()
614 .depth(cursor.par()->params().depth() - 1);
616 if (cursor.par() == selection.end.par()) {
619 cursor.par(cursor.par()->next());
622 redoParagraphs(bview, selection.start, endpar);
624 // we have to reset the selection, because the
625 // geometry could have changed
626 setCursor(bview, selection.start.par(),
627 selection.start.pos());
628 selection.cursor = cursor;
629 setCursor(bview, selection.end.par(), selection.end.pos());
630 updateCounters(bview);
633 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
637 // set font over selection and make a total rebreak of those paragraphs
638 void LyXText::setFont(BufferView * bview, LyXFont const & font, bool toggleall)
640 // if there is no selection just set the current_font
641 if (!selection.set()) {
642 // Determine basis font
644 if (cursor.pos() < cursor.par()->beginningOfBody()) {
645 layoutfont = getLabelFont(bview->buffer(),
648 layoutfont = getLayoutFont(bview->buffer(),
651 // Update current font
652 real_current_font.update(font,
653 bview->buffer()->params.language,
656 // Reduce to implicit settings
657 current_font = real_current_font;
658 current_font.reduce(layoutfont);
659 // And resolve it completely
660 real_current_font.realize(layoutfont);
665 LyXCursor tmpcursor = cursor; // store the current cursor
667 // ok we have a selection. This is always between sel_start_cursor
668 // and sel_end cursor
670 setUndo(bview, Undo::EDIT,
671 selection.start.par(), selection.end.par()->next());
673 cursor = selection.start;
674 while (cursor.par() != selection.end.par() ||
675 cursor.pos() < selection.end.pos())
677 if (cursor.pos() < cursor.par()->size()) {
678 // an open footnote should behave like a closed one
679 setCharFont(bview, cursor.par(), cursor.pos(),
681 cursor.pos(cursor.pos() + 1);
684 cursor.par(cursor.par()->next());
689 redoParagraphs(bview, selection.start, selection.end.par()->next());
691 // we have to reset the selection, because the
692 // geometry could have changed, but we keep
693 // it for user convenience
694 setCursor(bview, selection.start.par(), selection.start.pos());
695 selection.cursor = cursor;
696 setCursor(bview, selection.end.par(), selection.end.pos());
698 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true,
699 tmpcursor.boundary());
703 void LyXText::redoHeightOfParagraph(BufferView * bview)
705 Row * tmprow = cursor.row();
706 int y = cursor.y() - tmprow->baseline();
708 setHeightOfRow(bview, tmprow);
710 while (tmprow->previous()
711 && tmprow->previous()->par() == tmprow->par()) {
712 tmprow = tmprow->previous();
713 y -= tmprow->height();
714 setHeightOfRow(bview, tmprow);
717 // we can set the refreshing parameters now
718 status(bview, LyXText::NEED_MORE_REFRESH);
720 refresh_row = tmprow;
721 setCursor(bview, cursor.par(), cursor.pos(), false, cursor.boundary());
725 void LyXText::redoDrawingOfParagraph(BufferView * bview, LyXCursor const & cur)
727 Row * tmprow = cur.row();
729 int y = cur.y() - tmprow->baseline();
730 setHeightOfRow(bview, tmprow);
732 while (tmprow->previous()
733 && tmprow->previous()->par() == tmprow->par()) {
734 tmprow = tmprow->previous();
735 y -= tmprow->height();
738 // we can set the refreshing parameters now
739 if (status_ == LyXText::UNCHANGED || y < refresh_y) {
741 refresh_row = tmprow;
743 status(bview, LyXText::NEED_MORE_REFRESH);
744 setCursor(bview, cur.par(), cur.pos());
748 // deletes and inserts again all paragaphs between the cursor
749 // and the specified par
750 // This function is needed after SetLayout and SetFont etc.
751 void LyXText::redoParagraphs(BufferView * bview, LyXCursor const & cur,
752 Paragraph const * endpar) const
755 Paragraph * tmppar = 0;
756 Paragraph * first_phys_par = 0;
758 Row * tmprow = cur.row();
760 int y = cur.y() - tmprow->baseline();
762 if (!tmprow->previous()) {
763 // a trick/hack for UNDO
764 // This is needed because in an UNDO/REDO we could have changed
765 // the ownerParagrah() so the paragraph inside the row is NOT
766 // my really first par anymore. Got it Lars ;) (Jug 20011206)
767 first_phys_par = ownerParagraph();
769 first_phys_par = tmprow->par();
770 while (tmprow->previous()
771 && tmprow->previous()->par() == first_phys_par)
773 tmprow = tmprow->previous();
774 y -= tmprow->height();
778 // we can set the refreshing parameters now
779 status(bview, LyXText::NEED_MORE_REFRESH);
781 refresh_row = tmprow->previous(); /* the real refresh row will
782 be deleted, so I store
786 tmppar = tmprow->next()->par();
789 while (tmprow->next() && tmppar != endpar) {
790 removeRow(tmprow->next());
791 if (tmprow->next()) {
792 tmppar = tmprow->next()->par();
798 // remove the first one
799 tmprow2 = tmprow; /* this is because tmprow->previous()
801 tmprow = tmprow->previous();
804 tmppar = first_phys_par;
808 insertParagraph(bview, tmppar, tmprow);
812 while (tmprow->next()
813 && tmprow->next()->par() == tmppar) {
814 tmprow = tmprow->next();
816 tmppar = tmppar->next();
818 } while (tmppar && tmppar != endpar);
820 // this is because of layout changes
822 refresh_y -= refresh_row->height();
823 setHeightOfRow(bview, refresh_row);
825 refresh_row = firstrow;
827 setHeightOfRow(bview, refresh_row);
830 if (tmprow && tmprow->next())
831 setHeightOfRow(bview, tmprow->next());
832 updateCounters(bview);
836 void LyXText::fullRebreak(BufferView * bview)
842 if (need_break_row) {
843 breakAgain(bview, need_break_row);
850 // important for the screen
853 // the cursor set functions have a special mechanism. When they
854 // realize, that you left an empty paragraph, they will delete it.
855 // They also delete the corresponding row
857 // need the selection cursor:
858 void LyXText::setSelection(BufferView * bview)
860 bool const lsel = selection.set();
862 if (!selection.set()) {
863 last_sel_cursor = selection.cursor;
864 selection.start = selection.cursor;
865 selection.end = selection.cursor;
870 // first the toggling area
871 if (cursor.y() < last_sel_cursor.y()
872 || (cursor.y() == last_sel_cursor.y()
873 && cursor.x() < last_sel_cursor.x())) {
874 toggle_end_cursor = last_sel_cursor;
875 toggle_cursor = cursor;
877 toggle_end_cursor = cursor;
878 toggle_cursor = last_sel_cursor;
881 last_sel_cursor = cursor;
883 // and now the whole selection
885 if (selection.cursor.par() == cursor.par())
886 if (selection.cursor.pos() < cursor.pos()) {
887 selection.end = cursor;
888 selection.start = selection.cursor;
890 selection.end = selection.cursor;
891 selection.start = cursor;
893 else if (selection.cursor.y() < cursor.y() ||
894 (selection.cursor.y() == cursor.y()
895 && selection.cursor.x() < cursor.x())) {
896 selection.end = cursor;
897 selection.start = selection.cursor;
900 selection.end = selection.cursor;
901 selection.start = cursor;
904 // a selection with no contents is not a selection
905 if (selection.start.par() == selection.end.par() &&
906 selection.start.pos() == selection.end.pos())
907 selection.set(false);
909 if (inset_owner && (selection.set() || lsel))
910 inset_owner->setUpdateStatus(bview, InsetText::SELECTION);
914 string const LyXText::selectionAsString(Buffer const * buffer,
917 if (!selection.set()) return string();
919 // should be const ...
920 Paragraph * startpar(selection.start.par());
921 Paragraph * endpar(selection.end.par());
922 pos_type const startpos(selection.start.pos());
923 pos_type const endpos(selection.end.pos());
925 if (startpar == endpar) {
926 return startpar->asString(buffer, startpos, endpos, label);
931 // First paragraph in selection
932 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
934 // The paragraphs in between (if any)
935 LyXCursor tmpcur(selection.start);
936 tmpcur.par(tmpcur.par()->next());
937 while (tmpcur.par() != endpar) {
938 result += tmpcur.par()->asString(buffer, 0,
939 tmpcur.par()->size(),
941 tmpcur.par(tmpcur.par()->next());
944 // Last paragraph in selection
945 result += endpar->asString(buffer, 0, endpos, label);
951 void LyXText::clearSelection() const
953 selection.set(false);
954 selection.mark(false);
955 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
956 // reset this in the bv_owner!
957 if (bv_owner && bv_owner->text)
958 bv_owner->text->xsel_cache.set(false);
962 void LyXText::cursorHome(BufferView * bview) const
964 setCursor(bview, cursor.par(), cursor.row()->pos());
968 void LyXText::cursorEnd(BufferView * bview) const
970 if (!cursor.row()->next()
971 || cursor.row()->next()->par() != cursor.row()->par()) {
972 setCursor(bview, cursor.par(), cursor.row()->lastPos() + 1);
974 if (!cursor.par()->empty() &&
975 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
976 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
977 setCursor(bview, cursor.par(), cursor.row()->lastPos());
979 setCursor(bview,cursor.par(),
980 cursor.row()->lastPos() + 1);
986 void LyXText::cursorTop(BufferView * bview) const
988 while (cursor.par()->previous())
989 cursor.par(cursor.par()->previous());
990 setCursor(bview, cursor.par(), 0);
994 void LyXText::cursorBottom(BufferView * bview) const
996 while (cursor.par()->next())
997 cursor.par(cursor.par()->next());
998 setCursor(bview, cursor.par(), cursor.par()->size());
1002 void LyXText::toggleFree(BufferView * bview,
1003 LyXFont const & font, bool toggleall)
1005 // If the mask is completely neutral, tell user
1006 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
1007 // Could only happen with user style
1008 bview->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
1012 // Try implicit word selection
1013 // If there is a change in the language the implicit word selection
1015 LyXCursor resetCursor = cursor;
1016 bool implicitSelection = (font.language() == ignore_language
1017 && font.number() == LyXFont::IGNORE)
1018 ? selectWordWhenUnderCursor(bview, WHOLE_WORD_STRICT) : false;
1021 setFont(bview, font, toggleall);
1023 // Implicit selections are cleared afterwards
1024 //and cursor is set to the original position.
1025 if (implicitSelection) {
1027 cursor = resetCursor;
1028 setCursor(bview, cursor.par(), cursor.pos());
1029 selection.cursor = cursor;
1032 inset_owner->setUpdateStatus(bview, InsetText::CURSOR_PAR);
1036 string LyXText::getStringToIndex(BufferView * bview)
1038 // Try implicit word selection
1039 // If there is a change in the language the implicit word selection
1041 LyXCursor const reset_cursor = cursor;
1042 bool const implicitSelection = selectWordWhenUnderCursor(bview, PREVIOUS_WORD);
1045 if (!selection.set())
1046 bview->owner()->message(_("Nothing to index!"));
1047 else if (selection.start.par() != selection.end.par())
1048 bview->owner()->message(_("Cannot index more than one paragraph!"));
1050 idxstring = selectionAsString(bview->buffer(), false);
1052 // Reset cursors to their original position.
1053 cursor = reset_cursor;
1054 setCursor(bview, cursor.par(), cursor.pos());
1055 selection.cursor = cursor;
1057 // Clear the implicit selection.
1058 if (implicitSelection)
1065 // the DTP switches for paragraphs. LyX will store them in the first
1066 // physicla paragraph. When a paragraph is broken, the top settings rest,
1067 // the bottom settings are given to the new one. So I can make shure,
1068 // they do not duplicate themself and you cannnot make dirty things with
1071 void LyXText::setParagraph(BufferView * bview,
1072 bool line_top, bool line_bottom,
1073 bool pagebreak_top, bool pagebreak_bottom,
1074 VSpace const & space_top,
1075 VSpace const & space_bottom,
1076 Spacing const & spacing,
1078 string labelwidthstring,
1081 LyXCursor tmpcursor = cursor;
1082 if (!selection.set()) {
1083 selection.start = cursor;
1084 selection.end = cursor;
1087 // make sure that the depth behind the selection are restored, too
1088 Paragraph * endpar = selection.end.par()->next();
1089 Paragraph * undoendpar = endpar;
1091 if (endpar && endpar->getDepth()) {
1092 while (endpar && endpar->getDepth()) {
1093 endpar = endpar->next();
1094 undoendpar = endpar;
1098 // because of parindents etc.
1099 endpar = endpar->next();
1102 setUndo(bview, Undo::EDIT, selection.start.par(), undoendpar);
1105 Paragraph * tmppar = selection.end.par();
1107 while (tmppar != selection.start.par()->previous()) {
1108 setCursor(bview, tmppar, 0);
1109 status(bview, LyXText::NEED_MORE_REFRESH);
1110 refresh_row = cursor.row();
1111 refresh_y = cursor.y() - cursor.row()->baseline();
1112 cursor.par()->params().lineTop(line_top);
1113 cursor.par()->params().lineBottom(line_bottom);
1114 cursor.par()->params().pagebreakTop(pagebreak_top);
1115 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1116 cursor.par()->params().spaceTop(space_top);
1117 cursor.par()->params().spaceBottom(space_bottom);
1118 cursor.par()->params().spacing(spacing);
1119 // does the layout allow the new alignment?
1120 LyXLayout_ptr const & layout = cursor.par()->layout();
1122 if (align == LYX_ALIGN_LAYOUT)
1123 align = layout->align;
1124 if (align & layout->alignpossible) {
1125 if (align == layout->align)
1126 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1128 cursor.par()->params().align(align);
1130 cursor.par()->setLabelWidthString(labelwidthstring);
1131 cursor.par()->params().noindent(noindent);
1132 tmppar = cursor.par()->previous();
1135 redoParagraphs(bview, selection.start, endpar);
1138 setCursor(bview, selection.start.par(), selection.start.pos());
1139 selection.cursor = cursor;
1140 setCursor(bview, selection.end.par(), selection.end.pos());
1141 setSelection(bview);
1142 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
1144 bview->updateInset(inset_owner, true);
1148 // set the counter of a paragraph. This includes the labels
1149 void LyXText::setCounter(Buffer const * buf, Paragraph * par) const
1151 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1152 LyXLayout_ptr const & layout = par->layout();
1154 if (par->previous()) {
1156 par->params().appendix(par->previous()->params().appendix());
1157 if (!par->params().appendix() && par->params().startOfAppendix()) {
1158 par->params().appendix(true);
1159 textclass.counters().reset();
1161 par->enumdepth = par->previous()->enumdepth;
1162 par->itemdepth = par->previous()->itemdepth;
1164 par->params().appendix(par->params().startOfAppendix());
1169 /* Maybe we have to increment the enumeration depth.
1170 * BUT, enumeration in a footnote is considered in isolation from its
1171 * surrounding paragraph so don't increment if this is the
1172 * first line of the footnote
1173 * AND, bibliographies can't have their depth changed ie. they
1174 * are always of depth 0
1177 && par->previous()->getDepth() < par->getDepth()
1178 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1179 && par->enumdepth < 3
1180 && layout->labeltype != LABEL_BIBLIO) {
1184 // Maybe we have to decrement the enumeration depth, see note above
1186 && par->previous()->getDepth() > par->getDepth()
1187 && layout->labeltype != LABEL_BIBLIO) {
1188 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1191 if (!par->params().labelString().empty()) {
1192 par->params().labelString(string());
1195 if (layout->margintype == MARGIN_MANUAL) {
1196 if (par->params().labelWidthString().empty()) {
1197 par->setLabelWidthString(layout->labelstring());
1200 par->setLabelWidthString(string());
1203 // is it a layout that has an automatic label?
1204 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1205 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1209 if (i >= 0 && i <= buf->params.secnumdepth) {
1213 textclass.counters().step(layout->latexname());
1215 // Is there a label? Useful for Chapter layout
1216 if (!par->params().appendix()) {
1217 s << layout->labelstring();
1219 s << layout->labelstring_appendix();
1222 // Use of an integer is here less than elegant. For now.
1223 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1224 if (!par->params().appendix()) {
1225 numbertype = "sectioning";
1227 numbertype = "appendix";
1228 if (par->isRightToLeftPar(buf->params))
1229 langtype = "hebrew";
1234 s << textclass.counters()
1235 .numberLabel(layout->latexname(),
1236 numbertype, langtype, head);
1238 par->params().labelString(STRCONV(s.str()));
1240 // reset enum counters
1241 textclass.counters().reset("enum");
1242 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1243 textclass.counters().reset("enum");
1244 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1246 // Yes I know this is a really, really! bad solution
1248 string enumcounter("enum");
1250 switch (par->enumdepth) {
1259 enumcounter += "iv";
1262 // not a valid enumdepth...
1266 textclass.counters().step(enumcounter);
1268 s << textclass.counters()
1269 .numberLabel(enumcounter, "enumeration");
1270 par->params().labelString(STRCONV(s.str()));
1272 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1273 textclass.counters().step("bibitem");
1274 int number = textclass.counters().value("bibitem");
1275 if (par->bibitem()) {
1276 par->bibitem()->setCounter(number);
1277 par->params().labelString(layout->labelstring());
1279 // In biblio should't be following counters but...
1281 string s = layout->labelstring();
1283 // the caption hack:
1284 if (layout->labeltype == LABEL_SENSITIVE) {
1285 Paragraph * tmppar = par;
1288 while (tmppar && tmppar->inInset()
1289 // the single '=' is intended below
1290 && (in = tmppar->inInset()->owner())) {
1291 if (in->lyxCode() == Inset::FLOAT_CODE ||
1292 in->lyxCode() == Inset::WRAP_CODE) {
1296 tmppar = in->parOwner();
1302 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1304 textclass.counters().step(fl.type());
1306 // Doesn't work... yet.
1307 #warning use boost.format
1308 #if USE_BOOST_FORMAT
1309 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1310 // s << boost::format(_("%1$s %1$d:")
1312 // % buf->counters().value(fl.name());
1315 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1316 o << fl.name() << " #:";
1317 s = STRCONV(o.str());
1320 // par->SetLayout(0);
1321 // s = layout->labelstring;
1322 s = _("Senseless: ");
1325 par->params().labelString(s);
1327 // reset the enumeration counter. They are always reset
1328 // when there is any other layout between
1329 // Just fall-through between the cases so that all
1330 // enum counters deeper than enumdepth is also reset.
1331 switch (par->enumdepth) {
1333 textclass.counters().reset("enumi");
1335 textclass.counters().reset("enumii");
1337 textclass.counters().reset("enumiii");
1339 textclass.counters().reset("enumiv");
1345 // Updates all counters. Paragraphs with changed label string will be rebroken
1346 void LyXText::updateCounters(BufferView * bview) const
1348 Row * row = firstrow;
1349 Paragraph * par = row->par();
1351 // CHECK if this is really needed. (Lgb)
1352 bview->buffer()->params.getLyXTextClass().counters().reset();
1355 while (row->par() != par)
1358 string const oldLabel = par->params().labelString();
1360 // setCounter can potentially change the labelString.
1361 setCounter(bview->buffer(), par);
1363 string const & newLabel = par->params().labelString();
1365 if (oldLabel.empty() && !newLabel.empty()) {
1366 removeParagraph(row);
1367 appendParagraph(bview, row);
1375 void LyXText::insertInset(BufferView * bview, Inset * inset)
1377 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1379 setUndo(bview, Undo::FINISH, cursor.par(), cursor.par()->next());
1381 cursor.par()->insertInset(cursor.pos(), inset);
1382 // Just to rebreak and refresh correctly.
1383 // The character will not be inserted a second time
1384 insertChar(bview, Paragraph::META_INSET);
1385 // If we enter a highly editable inset the cursor should be to before
1386 // the inset. This couldn't happen before as Undo was not handled inside
1387 // inset now after the Undo LyX tries to call inset->Edit(...) again
1388 // and cannot do this as the cursor is behind the inset and GetInset
1389 // does not return the inset!
1390 if (isHighlyEditableInset(inset)) {
1391 cursorLeft(bview, true);
1397 void LyXText::copyEnvironmentType()
1399 copylayouttype = cursor.par()->layout()->name();
1403 void LyXText::pasteEnvironmentType(BufferView * bview)
1405 // do nothing if there has been no previous copyEnvironmentType()
1406 if (!copylayouttype.empty())
1407 setLayout(bview, copylayouttype);
1411 void LyXText::cutSelection(BufferView * bview, bool doclear, bool realcut)
1413 // Stuff what we got on the clipboard. Even if there is no selection.
1415 // There is a problem with having the stuffing here in that the
1416 // larger the selection the slower LyX will get. This can be
1417 // solved by running the line below only when the selection has
1418 // finished. The solution used currently just works, to make it
1419 // faster we need to be more clever and probably also have more
1420 // calls to stuffClipboard. (Lgb)
1421 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1423 // This doesn't make sense, if there is no selection
1424 if (!selection.set())
1427 // OK, we have a selection. This is always between selection.start
1428 // and selection.end
1430 // make sure that the depth behind the selection are restored, too
1431 Paragraph * endpar = selection.end.par()->next();
1432 Paragraph * undoendpar = endpar;
1434 if (endpar && endpar->getDepth()) {
1435 while (endpar && endpar->getDepth()) {
1436 endpar = endpar->next();
1437 undoendpar = endpar;
1439 } else if (endpar) {
1440 endpar = endpar->next(); // because of parindents etc.
1443 setUndo(bview, Undo::DELETE,
1444 selection.start.par(), undoendpar);
1446 // there are two cases: cut only within one paragraph or
1447 // more than one paragraph
1448 if (selection.start.par() == selection.end.par()) {
1449 // only within one paragraph
1450 endpar = selection.end.par();
1451 int pos = selection.end.pos();
1452 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1453 selection.start.pos(), pos,
1454 bview->buffer()->params.textclass,
1456 selection.end.pos(pos);
1458 endpar = selection.end.par();
1459 int pos = selection.end.pos();
1460 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1461 selection.start.pos(), pos,
1462 bview->buffer()->params.textclass,
1465 selection.end.par(endpar);
1466 selection.end.pos(pos);
1467 cursor.pos(selection.end.pos());
1469 endpar = endpar->next();
1471 // sometimes necessary
1473 selection.start.par()->stripLeadingSpaces();
1475 redoParagraphs(bview, selection.start, endpar);
1477 // cutSelection can invalidate the cursor so we need to set
1479 // we prefer the end for when tracking changes
1480 cursor = selection.end;
1482 // need a valid cursor. (Lgb)
1485 setCursor(bview, cursor.par(), cursor.pos());
1486 selection.cursor = cursor;
1487 updateCounters(bview);
1491 void LyXText::copySelection(BufferView * bview)
1493 // stuff the selection onto the X clipboard, from an explicit copy request
1494 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1496 // this doesnt make sense, if there is no selection
1497 if (!selection.set())
1500 // ok we have a selection. This is always between selection.start
1501 // and sel_end cursor
1503 // copy behind a space if there is one
1504 while (selection.start.par()->size() > selection.start.pos()
1505 && selection.start.par()->isLineSeparator(selection.start.pos())
1506 && (selection.start.par() != selection.end.par()
1507 || selection.start.pos() < selection.end.pos()))
1508 selection.start.pos(selection.start.pos() + 1);
1510 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1511 selection.start.pos(), selection.end.pos(),
1512 bview->buffer()->params.textclass);
1516 void LyXText::pasteSelection(BufferView * bview)
1518 // this does not make sense, if there is nothing to paste
1519 if (!CutAndPaste::checkPastePossible())
1522 setUndo(bview, Undo::INSERT,
1523 cursor.par(), cursor.par()->next());
1526 Paragraph * actpar = cursor.par();
1527 int pos = cursor.pos();
1529 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1530 bview->buffer()->params.textclass);
1532 redoParagraphs(bview, cursor, endpar);
1534 setCursor(bview, cursor.par(), cursor.pos());
1537 selection.cursor = cursor;
1538 setCursor(bview, actpar, pos);
1539 setSelection(bview);
1540 updateCounters(bview);
1544 void LyXText::setSelectionRange(BufferView * bview, lyx::pos_type length)
1549 selection.cursor = cursor;
1552 setSelection(bview);
1556 // simple replacing. The font of the first selected character is used
1557 void LyXText::replaceSelectionWithString(BufferView * bview,
1560 setCursorParUndo(bview);
1563 if (!selection.set()) { // create a dummy selection
1564 selection.end = cursor;
1565 selection.start = cursor;
1568 // Get font setting before we cut
1569 pos_type pos = selection.end.pos();
1570 LyXFont const font = selection.start.par()
1571 ->getFontSettings(bview->buffer()->params,
1572 selection.start.pos());
1574 // Insert the new string
1575 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1576 selection.end.par()->insertChar(pos, (*cit), font);
1580 // Cut the selection
1581 cutSelection(bview, true, false);
1587 // needed to insert the selection
1588 void LyXText::insertStringAsLines(BufferView * bview, string const & str)
1590 Paragraph * par = cursor.par();
1591 pos_type pos = cursor.pos();
1592 Paragraph * endpar = cursor.par()->next();
1594 setCursorParUndo(bview);
1596 // only to be sure, should not be neccessary
1599 bview->buffer()->insertStringAsLines(par, pos, current_font, str);
1601 redoParagraphs(bview, cursor, endpar);
1602 setCursor(bview, cursor.par(), cursor.pos());
1603 selection.cursor = cursor;
1604 setCursor(bview, par, pos);
1605 setSelection(bview);
1609 // turns double-CR to single CR, others where converted into one
1610 // blank. Then InsertStringAsLines is called
1611 void LyXText::insertStringAsParagraphs(BufferView * bview, string const & str)
1613 string linestr(str);
1614 bool newline_inserted = false;
1615 for (string::size_type i = 0; i < linestr.length(); ++i) {
1616 if (linestr[i] == '\n') {
1617 if (newline_inserted) {
1618 // we know that \r will be ignored by
1619 // InsertStringA. Of course, it is a dirty
1620 // trick, but it works...
1621 linestr[i - 1] = '\r';
1625 newline_inserted = true;
1627 } else if (IsPrintable(linestr[i])) {
1628 newline_inserted = false;
1631 insertStringAsLines(bview, linestr);
1635 void LyXText::checkParagraph(BufferView * bview, Paragraph * par,
1638 LyXCursor tmpcursor;
1642 Row * row = getRow(par, pos, y);
1644 // is there a break one row above
1645 if (row->previous() && row->previous()->par() == row->par()) {
1646 z = rowBreakPoint(*bview, *row->previous());
1647 if (z >= row->pos()) {
1648 // set the dimensions of the row above
1649 y -= row->previous()->height();
1651 refresh_row = row->previous();
1652 status(bview, LyXText::NEED_MORE_REFRESH);
1654 breakAgain(bview, row->previous());
1656 // set the cursor again. Otherwise
1657 // dangling pointers are possible
1658 setCursor(bview, cursor.par(), cursor.pos(),
1659 false, cursor.boundary());
1660 selection.cursor = cursor;
1665 int const tmpheight = row->height();
1666 pos_type const tmplast = row->lastPos();
1670 breakAgain(bview, row);
1671 if (row->height() == tmpheight && row->lastPos() == tmplast)
1672 status(bview, LyXText::NEED_VERY_LITTLE_REFRESH);
1674 status(bview, LyXText::NEED_MORE_REFRESH);
1676 // check the special right address boxes
1677 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1684 redoDrawingOfParagraph(bview, tmpcursor);
1687 // set the cursor again. Otherwise dangling pointers are possible
1688 // also set the selection
1690 if (selection.set()) {
1692 setCursorIntern(bview, selection.cursor.par(), selection.cursor.pos(),
1693 false, selection.cursor.boundary());
1694 selection.cursor = cursor;
1695 setCursorIntern(bview, selection.start.par(),
1696 selection.start.pos(),
1697 false, selection.start.boundary());
1698 selection.start = cursor;
1699 setCursorIntern(bview, selection.end.par(),
1700 selection.end.pos(),
1701 false, selection.end.boundary());
1702 selection.end = cursor;
1703 setCursorIntern(bview, last_sel_cursor.par(),
1704 last_sel_cursor.pos(),
1705 false, last_sel_cursor.boundary());
1706 last_sel_cursor = cursor;
1709 setCursorIntern(bview, cursor.par(), cursor.pos(),
1710 false, cursor.boundary());
1714 // returns false if inset wasn't found
1715 bool LyXText::updateInset(BufferView * bview, Inset * inset)
1717 // first check the current paragraph
1718 int pos = cursor.par()->getPositionOfInset(inset);
1720 checkParagraph(bview, cursor.par(), pos);
1724 // check every paragraph
1726 Paragraph * par = ownerParagraph();
1728 pos = par->getPositionOfInset(inset);
1730 checkParagraph(bview, par, pos);
1740 bool LyXText::setCursor(BufferView * bview, Paragraph * par,
1742 bool setfont, bool boundary) const
1744 LyXCursor old_cursor = cursor;
1745 setCursorIntern(bview, par, pos, setfont, boundary);
1746 return deleteEmptyParagraphMechanism(bview, old_cursor);
1750 void LyXText::setCursor(BufferView * bview, LyXCursor & cur, Paragraph * par,
1751 pos_type pos, bool boundary) const
1758 cur.boundary(boundary);
1760 // get the cursor y position in text
1762 Row * row = getRow(par, pos, y);
1763 Row * old_row = row;
1765 // if we are before the first char of this row and are still in the
1766 // same paragraph and there is a previous row then put the cursor on
1767 // the end of the previous row
1768 cur.iy(y + row->baseline());
1770 if (row->previous() && pos &&
1771 row->previous()->par() == row->par() &&
1772 pos < par->size() &&
1773 par->getChar(pos) == Paragraph::META_INSET &&
1774 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1776 row = row->previous();
1781 // y is now the beginning of the cursor row
1782 y += row->baseline();
1783 // y is now the cursor baseline
1786 pos_type last = old_row->lastPrintablePos();
1788 // None of these should happen, but we're scaredy-cats
1789 if (pos > par->size()) {
1790 lyxerr << "dont like 1 please report" << endl;
1793 } else if (pos > last + 1) {
1794 lyxerr << "dont like 2 please report" << endl;
1795 // This shouldn't happen.
1798 } else if (pos < row->pos()) {
1799 lyxerr << "dont like 3 please report" << endl;
1804 // now get the cursors x position
1805 float x = getCursorX(bview, row, pos, last, boundary);
1808 if (old_row != row) {
1809 x = getCursorX(bview, old_row, pos, last, boundary);
1816 float LyXText::getCursorX(BufferView * bview, Row * row,
1817 pos_type pos, pos_type last, bool boundary) const
1819 pos_type cursor_vpos = 0;
1821 float fill_separator;
1823 float fill_label_hfill;
1824 // This call HAS to be here because of the BidiTables!!!
1825 prepareToPrint(bview, row, x, fill_separator, fill_hfill,
1828 if (last < row->pos())
1829 cursor_vpos = row->pos();
1830 else if (pos > last && !boundary)
1831 cursor_vpos = (row->par()->isRightToLeftPar(bview->buffer()->params))
1832 ? row->pos() : last + 1;
1833 else if (pos > row->pos() &&
1834 (pos > last || boundary))
1835 /// Place cursor after char at (logical) position pos - 1
1836 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1837 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1839 /// Place cursor before char at (logical) position pos
1840 cursor_vpos = (bidi_level(pos) % 2 == 0)
1841 ? log2vis(pos) : log2vis(pos) + 1;
1843 pos_type body_pos = row->par()->beginningOfBody();
1844 if ((body_pos > 0) &&
1845 ((body_pos-1 > last) ||
1846 !row->par()->isLineSeparator(body_pos-1)))
1849 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1850 pos_type pos = vis2log(vpos);
1851 if (body_pos > 0 && pos == body_pos - 1) {
1852 x += fill_label_hfill +
1853 font_metrics::width(
1854 row->par()->layout()->labelsep,
1855 getLabelFont(bview->buffer(),
1857 if (row->par()->isLineSeparator(body_pos - 1))
1858 x -= singleWidth(bview,
1859 row->par(), body_pos - 1);
1861 if (row->hfillExpansion(pos)) {
1862 x += singleWidth(bview, row->par(), pos);
1863 if (pos >= body_pos)
1866 x += fill_label_hfill;
1867 } else if (row->par()->isSeparator(pos)) {
1868 x += singleWidth(bview, row->par(), pos);
1869 if (pos >= body_pos)
1870 x += fill_separator;
1872 x += singleWidth(bview, row->par(), pos);
1878 void LyXText::setCursorIntern(BufferView * bview, Paragraph * par,
1879 pos_type pos, bool setfont, bool boundary) const
1881 InsetText * it = static_cast<InsetText *>(par->inInset());
1883 if (it != inset_owner) {
1884 lyxerr[Debug::INSETS] << "InsetText is " << it
1886 << "inset_owner is "
1887 << inset_owner << endl;
1888 #ifdef WITH_WARNINGS
1889 #warning I believe this code is wrong. (Lgb)
1890 #warning Jürgen, have a look at this. (Lgb)
1891 #warning Hmmm, I guess you are right but we
1892 #warning should verify when this is needed
1894 // Jürgen, would you like to have a look?
1895 // I guess we need to move the outer cursor
1896 // and open and lock the inset (bla bla bla)
1897 // stuff I don't know... so can you have a look?
1899 // I moved the lyxerr stuff in here so we can see if
1900 // this is actually really needed and where!
1902 // it->getLyXText(bview)->setCursorIntern(bview, par, pos, setfont, boundary);
1907 setCursor(bview, cursor, par, pos, boundary);
1909 setCurrentFont(bview);
1913 void LyXText::setCurrentFont(BufferView * bview) const
1915 pos_type pos = cursor.pos();
1916 if (cursor.boundary() && pos > 0)
1920 if (pos == cursor.par()->size())
1922 else // potentional bug... BUG (Lgb)
1923 if (cursor.par()->isSeparator(pos)) {
1924 if (pos > cursor.row()->pos() &&
1925 bidi_level(pos) % 2 ==
1926 bidi_level(pos - 1) % 2)
1928 else if (pos + 1 < cursor.par()->size())
1934 cursor.par()->getFontSettings(bview->buffer()->params, pos);
1935 real_current_font = getFont(bview->buffer(), cursor.par(), pos);
1937 if (cursor.pos() == cursor.par()->size() &&
1938 isBoundary(bview->buffer(), cursor.par(), cursor.pos()) &&
1939 !cursor.boundary()) {
1940 Language const * lang =
1941 cursor.par()->getParLanguage(bview->buffer()->params);
1942 current_font.setLanguage(lang);
1943 current_font.setNumber(LyXFont::OFF);
1944 real_current_font.setLanguage(lang);
1945 real_current_font.setNumber(LyXFont::OFF);
1950 // returns the column near the specified x-coordinate of the row
1951 // x is set to the real beginning of this column
1953 LyXText::getColumnNearX(BufferView * bview, Row * row, int & x,
1954 bool & boundary) const
1957 float fill_separator;
1959 float fill_label_hfill;
1961 prepareToPrint(bview, row, tmpx, fill_separator,
1962 fill_hfill, fill_label_hfill);
1964 pos_type vc = row->pos();
1965 pos_type last = row->lastPrintablePos();
1968 LyXLayout_ptr const & layout = row->par()->layout();
1970 bool left_side = false;
1972 pos_type body_pos = row->par()->beginningOfBody();
1973 float last_tmpx = tmpx;
1976 (body_pos - 1 > last ||
1977 !row->par()->isLineSeparator(body_pos - 1)))
1980 // check for empty row
1981 if (!row->par()->size()) {
1986 while (vc <= last && tmpx <= x) {
1989 if (body_pos > 0 && c == body_pos-1) {
1990 tmpx += fill_label_hfill +
1991 font_metrics::width(layout->labelsep,
1992 getLabelFont(bview->buffer(), row->par()));
1993 if (row->par()->isLineSeparator(body_pos - 1))
1994 tmpx -= singleWidth(bview, row->par(), body_pos-1);
1997 if (row->hfillExpansion(c)) {
1998 tmpx += singleWidth(bview, row->par(), c);
2002 tmpx += fill_label_hfill;
2003 } else if (row->par()->isSeparator(c)) {
2004 tmpx += singleWidth(bview, row->par(), c);
2006 tmpx+= fill_separator;
2008 tmpx += singleWidth(bview, row->par(), c);
2013 if ((tmpx + last_tmpx) / 2 > x) {
2018 if (vc > last + 1) // This shouldn't happen.
2022 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
2023 // some speedup if rtl_support=false
2024 && (!row->next() || row->next()->par() != row->par());
2025 bool const rtl = (lastrow)
2026 ? row->par()->isRightToLeftPar(bview->buffer()->params)
2027 : false; // If lastrow is false, we don't need to compute
2028 // the value of rtl.
2031 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
2032 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
2034 else if (vc == row->pos()) {
2036 if (bidi_level(c) % 2 == 1)
2039 c = vis2log(vc - 1);
2040 bool const rtl = (bidi_level(c) % 2 == 1);
2041 if (left_side == rtl) {
2043 boundary = isBoundary(bview->buffer(), row->par(), c);
2047 if (row->pos() <= last && c > last
2048 && row->par()->isNewline(last)) {
2049 if (bidi_level(last) % 2 == 0)
2050 tmpx -= singleWidth(bview, row->par(), last);
2052 tmpx += singleWidth(bview, row->par(), last);
2062 void LyXText::setCursorFromCoordinates(BufferView * bview, int x, int y) const
2064 LyXCursor old_cursor = cursor;
2066 setCursorFromCoordinates(bview, cursor, x, y);
2067 setCurrentFont(bview);
2068 deleteEmptyParagraphMechanism(bview, old_cursor);
2075 * return true if the cursor given is at the end of a row,
2076 * and the next row is filled by an inset that spans an entire
2079 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2082 Row const & next = *row.next();
2084 if (next.pos() != cur.pos() || next.par() != cur.par())
2086 if (!cur.par()->isInset(cur.pos()))
2088 Inset const * inset = cur.par()->getInset(cur.pos());
2089 if (inset->needFullRow() || inset->display())
2096 void LyXText::setCursorFromCoordinates(BufferView * bview, LyXCursor & cur,
2099 // Get the row first.
2101 Row * row = getRowNearY(y);
2103 pos_type const column = getColumnNearX(bview, row, x, bound);
2104 cur.par(row->par());
2105 cur.pos(row->pos() + column);
2107 cur.y(y + row->baseline());
2110 if (beforeFullRowInset(*row, cur)) {
2111 pos_type last = row->lastPrintablePos();
2112 float x = getCursorX(bview, row->next(), cur.pos(), last, bound);
2114 cur.iy(y + row->height() + row->next()->baseline());
2115 cur.irow(row->next());
2121 cur.boundary(bound);
2125 void LyXText::cursorLeft(BufferView * bview, bool internal) const
2127 if (cursor.pos() > 0) {
2128 bool boundary = cursor.boundary();
2129 setCursor(bview, cursor.par(), cursor.pos() - 1, true, false);
2130 if (!internal && !boundary &&
2131 isBoundary(bview->buffer(), cursor.par(), cursor.pos() + 1))
2132 setCursor(bview, cursor.par(), cursor.pos() + 1, true, true);
2133 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2134 Paragraph * par = cursor.par()->previous();
2135 setCursor(bview, par, par->size());
2140 void LyXText::cursorRight(BufferView * bview, bool internal) const
2142 if (!internal && cursor.boundary() &&
2143 !cursor.par()->isNewline(cursor.pos()))
2144 setCursor(bview, cursor.par(), cursor.pos(), true, false);
2145 else if (cursor.pos() < cursor.par()->size()) {
2146 setCursor(bview, cursor.par(), cursor.pos() + 1, true, false);
2148 isBoundary(bview->buffer(), cursor.par(), cursor.pos()))
2149 setCursor(bview, cursor.par(), cursor.pos(), true, true);
2150 } else if (cursor.par()->next())
2151 setCursor(bview, cursor.par()->next(), 0);
2155 void LyXText::cursorUp(BufferView * bview, bool selecting) const
2158 int x = cursor.x_fix();
2159 int y = cursor.y() - cursor.row()->baseline() - 1;
2160 setCursorFromCoordinates(bview, x, y);
2163 int y1 = cursor.iy() - topy;
2166 Inset * inset_hit = checkInsetHit(bview, x, y1);
2167 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2168 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2172 setCursorFromCoordinates(bview, cursor.x_fix(),
2173 cursor.y() - cursor.row()->baseline() - 1);
2178 void LyXText::cursorDown(BufferView * bview, bool selecting) const
2181 int x = cursor.x_fix();
2182 int y = cursor.y() - cursor.row()->baseline() +
2183 cursor.row()->height() + 1;
2184 setCursorFromCoordinates(bview, x, y);
2185 if (!selecting && cursor.row() == cursor.irow()) {
2187 int y1 = cursor.iy() - topy;
2190 Inset * inset_hit = checkInsetHit(bview, x, y1);
2191 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2192 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2196 setCursorFromCoordinates(bview, cursor.x_fix(),
2197 cursor.y() - cursor.row()->baseline()
2198 + cursor.row()->height() + 1);
2203 void LyXText::cursorUpParagraph(BufferView * bview) const
2205 if (cursor.pos() > 0) {
2206 setCursor(bview, cursor.par(), 0);
2208 else if (cursor.par()->previous()) {
2209 setCursor(bview, cursor.par()->previous(), 0);
2214 void LyXText::cursorDownParagraph(BufferView * bview) const
2216 if (cursor.par()->next()) {
2217 setCursor(bview, cursor.par()->next(), 0);
2219 setCursor(bview, cursor.par(), cursor.par()->size());
2223 // fix the cursor `cur' after a characters has been deleted at `where'
2224 // position. Called by deleteEmptyParagraphMechanism
2225 void LyXText::fixCursorAfterDelete(BufferView * bview,
2227 LyXCursor const & where) const
2229 // if cursor is not in the paragraph where the delete occured,
2231 if (cur.par() != where.par())
2234 // if cursor position is after the place where the delete occured,
2236 if (cur.pos() > where.pos())
2237 cur.pos(cur.pos()-1);
2239 // check also if we don't want to set the cursor on a spot behind the
2240 // pagragraph because we erased the last character.
2241 if (cur.pos() > cur.par()->size())
2242 cur.pos(cur.par()->size());
2244 // recompute row et al. for this cursor
2245 setCursor(bview, cur, cur.par(), cur.pos(), cur.boundary());
2249 bool LyXText::deleteEmptyParagraphMechanism(BufferView * bview,
2250 LyXCursor const & old_cursor) const
2252 // Would be wrong to delete anything if we have a selection.
2253 if (selection.set())
2256 // We allow all kinds of "mumbo-jumbo" when freespacing.
2257 if (old_cursor.par()->layout()->free_spacing
2258 || old_cursor.par()->isFreeSpacing()) {
2262 /* Ok I'll put some comments here about what is missing.
2263 I have fixed BackSpace (and thus Delete) to not delete
2264 double-spaces automagically. I have also changed Cut,
2265 Copy and Paste to hopefully do some sensible things.
2266 There are still some small problems that can lead to
2267 double spaces stored in the document file or space at
2268 the beginning of paragraphs. This happens if you have
2269 the cursor betwenn to spaces and then save. Or if you
2270 cut and paste and the selection have a space at the
2271 beginning and then save right after the paste. I am
2272 sure none of these are very hard to fix, but I will
2273 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2274 that I can get some feedback. (Lgb)
2277 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2278 // delete the LineSeparator.
2281 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2282 // delete the LineSeparator.
2285 // If the pos around the old_cursor were spaces, delete one of them.
2286 if (old_cursor.par() != cursor.par()
2287 || old_cursor.pos() != cursor.pos()) {
2288 // Only if the cursor has really moved
2290 if (old_cursor.pos() > 0
2291 && old_cursor.pos() < old_cursor.par()->size()
2292 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2293 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2294 old_cursor.par()->erase(old_cursor.pos() - 1);
2295 redoParagraphs(bview, old_cursor, old_cursor.par()->next());
2297 #ifdef WITH_WARNINGS
2298 #warning This will not work anymore when we have multiple views of the same buffer
2299 // In this case, we will have to correct also the cursors held by
2300 // other bufferviews. It will probably be easier to do that in a more
2301 // automated way in LyXCursor code. (JMarc 26/09/2001)
2303 // correct all cursors held by the LyXText
2304 fixCursorAfterDelete(bview, cursor, old_cursor);
2305 fixCursorAfterDelete(bview, selection.cursor,
2307 fixCursorAfterDelete(bview, selection.start,
2309 fixCursorAfterDelete(bview, selection.end, old_cursor);
2310 fixCursorAfterDelete(bview, last_sel_cursor,
2312 fixCursorAfterDelete(bview, toggle_cursor, old_cursor);
2313 fixCursorAfterDelete(bview, toggle_end_cursor,
2319 // don't delete anything if this is the ONLY paragraph!
2320 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2323 // Do not delete empty paragraphs with keepempty set.
2324 if (old_cursor.par()->layout()->keepempty)
2327 // only do our magic if we changed paragraph
2328 if (old_cursor.par() == cursor.par())
2331 // record if we have deleted a paragraph
2332 // we can't possibly have deleted a paragraph before this point
2333 bool deleted = false;
2335 if ((old_cursor.par()->empty()
2336 || (old_cursor.par()->size() == 1
2337 && old_cursor.par()->isLineSeparator(0)))) {
2338 // ok, we will delete anything
2339 LyXCursor tmpcursor;
2341 // make sure that you do not delete any environments
2342 status(bview, LyXText::NEED_MORE_REFRESH);
2345 if (old_cursor.row()->previous()) {
2346 refresh_row = old_cursor.row()->previous();
2347 refresh_y = old_cursor.y() - old_cursor.row()->baseline() - refresh_row->height();
2349 cursor = old_cursor; // that undo can restore the right cursor position
2350 Paragraph * endpar = old_cursor.par()->next();
2351 if (endpar && endpar->getDepth()) {
2352 while (endpar && endpar->getDepth()) {
2353 endpar = endpar->next();
2356 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2360 removeRow(old_cursor.row());
2361 if (ownerParagraph() == old_cursor.par()) {
2362 ownerParagraph(ownerParagraph()->next());
2365 delete old_cursor.par();
2367 /* Breakagain the next par. Needed because of
2368 * the parindent that can occur or dissappear.
2369 * The next row can change its height, if
2370 * there is another layout before */
2371 if (refresh_row->next()) {
2372 breakAgain(bview, refresh_row->next());
2373 updateCounters(bview);
2375 setHeightOfRow(bview, refresh_row);
2377 refresh_row = old_cursor.row()->next();
2378 refresh_y = old_cursor.y() - old_cursor.row()->baseline();
2381 cursor = old_cursor; // that undo can restore the right cursor position
2382 Paragraph * endpar = old_cursor.par()->next();
2383 if (endpar && endpar->getDepth()) {
2384 while (endpar && endpar->getDepth()) {
2385 endpar = endpar->next();
2388 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2392 removeRow(old_cursor.row());
2394 if (ownerParagraph() == old_cursor.par()) {
2395 ownerParagraph(ownerParagraph()->next());
2398 delete old_cursor.par();
2400 /* Breakagain the next par. Needed because of
2401 the parindent that can occur or dissappear.
2402 The next row can change its height, if
2403 there is another layout before */
2405 breakAgain(bview, refresh_row);
2406 updateCounters(bview);
2411 setCursorIntern(bview, cursor.par(), cursor.pos());
2413 if (selection.cursor.par() == old_cursor.par()
2414 && selection.cursor.pos() == old_cursor.pos()) {
2415 // correct selection
2416 selection.cursor = cursor;
2420 if (old_cursor.par()->stripLeadingSpaces()) {
2421 redoParagraphs(bview, old_cursor,
2422 old_cursor.par()->next());
2424 setCursorIntern(bview, cursor.par(), cursor.pos());
2425 selection.cursor = cursor;
2432 Paragraph * LyXText::ownerParagraph() const
2435 return inset_owner->paragraph();
2437 return &*(bv_owner->buffer()->paragraphs.begin());
2441 void LyXText::ownerParagraph(Paragraph * p) const
2444 inset_owner->paragraph(p);
2446 bv_owner->buffer()->paragraphs.set(p);
2451 void LyXText::ownerParagraph(int id, Paragraph * p) const
2453 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2454 if (op && op->inInset()) {
2455 static_cast<InsetText *>(op->inInset())->paragraph(p);
2462 LyXText::text_status LyXText::status() const
2468 void LyXText::status(BufferView * bview, LyXText::text_status new_status) const
2470 // We should not lose information from previous status
2471 // sets, or we'll forget to repaint the other bits
2472 // covered by the NEED_MORE_REFRESH
2473 if (new_status == NEED_VERY_LITTLE_REFRESH
2474 && status_ == NEED_MORE_REFRESH)
2477 status_ = new_status;
2479 if (new_status == UNCHANGED)
2485 LyXText * t = bview->text;
2487 // We are an inset's lyxtext. Tell the top-level lyxtext
2488 // it needs to update the row we're in.
2489 t->status(bview, NEED_VERY_LITTLE_REFRESH);
2490 if (!t->refresh_row) {
2491 t->refresh_row = t->cursor.row();
2492 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2497 bool LyXText::isTopLevel() const
2499 /// only the top-level lyxtext has a non-null bv owner
2504 bool LyXText::isInInset() const
2510 int defaultRowHeight()
2512 LyXFont const font(LyXFont::ALL_SANE);
2513 return int(font_metrics::maxAscent(font)
2514 + font_metrics::maxDescent(font) * 1.5);