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), first_y(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), first_y(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();
86 first_y = refresh_y = 0;
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->beginningOfMainBody()) {
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->beginningOfMainBody()) {
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->beginningOfMainBody())
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 behind 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
330 height -= row->height(); // the text becomes smaller
336 // remove all following rows of the paragraph of the specified row.
337 void LyXText::removeParagraph(Row * row) const
339 Paragraph * tmppar = row->par();
343 while (row && row->par() == tmppar) {
344 tmprow = row->next();
351 // insert the specified paragraph behind the specified row
352 void LyXText::insertParagraph(BufferView * bview, Paragraph * par,
355 // insert a new row, starting at position 0
356 insertRow(row, par, 0);
358 // and now append the whole paragraph behind the new row
361 appendParagraph(bview, firstrow);
363 row->next()->height(0);
364 appendParagraph(bview, row->next());
369 Inset * LyXText::getInset() const
371 if (cursor.pos() < cursor.par()->size()
372 && cursor.par()->isInset(cursor.pos())) {
373 return cursor.par()->getInset(cursor.pos());
379 void LyXText::toggleInset(BufferView * bview)
381 Inset * inset = getInset();
382 // is there an editable inset at cursor position?
383 if (!isEditableInset(inset)) {
384 // No, try to see if we are inside a collapsable inset
385 if (inset_owner && inset_owner->owner()
386 && inset_owner->owner()->isOpen()) {
387 bview->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
388 inset_owner->owner()->close(bview);
389 bview->getLyXText()->cursorRight(bview);
393 //bview->owner()->message(inset->editMessage());
395 // do we want to keep this?? (JMarc)
396 if (!isHighlyEditableInset(inset))
397 setCursorParUndo(bview);
399 if (inset->isOpen()) {
405 inset->open(bview, !inset->isOpen());
410 /* used in setlayout */
411 // Asger is not sure we want to do this...
412 void LyXText::makeFontEntriesLayoutSpecific(Buffer const * buf,
415 LyXLayout_ptr const & layout = par->layout();
418 for (pos_type pos = 0; pos < par->size(); ++pos) {
419 if (pos < par->beginningOfMainBody())
420 layoutfont = layout->labelfont;
422 layoutfont = layout->font;
424 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
425 tmpfont.reduce(layoutfont);
426 par->setFont(pos, tmpfont);
431 Paragraph * LyXText::setLayout(BufferView * bview,
432 LyXCursor & cur, LyXCursor & sstart_cur,
433 LyXCursor & send_cur,
434 string const & layout)
436 Paragraph * endpar = send_cur.par()->next();
437 Paragraph * undoendpar = endpar;
439 if (endpar && endpar->getDepth()) {
440 while (endpar && endpar->getDepth()) {
441 endpar = endpar->next();
445 endpar = endpar->next(); // because of parindents etc.
448 setUndo(bview, Undo::EDIT, sstart_cur.par(), undoendpar);
450 // ok we have a selection. This is always between sstart_cur
451 // and sel_end cursor
453 Paragraph * par = sstart_cur.par();
454 Paragraph * epar = send_cur.par()->next();
456 LyXLayout_ptr const & lyxlayout =
457 bview->buffer()->params.getLyXTextClass()[layout];
460 par->applyLayout(lyxlayout);
461 makeFontEntriesLayoutSpecific(bview->buffer(), par);
462 Paragraph * fppar = par;
463 fppar->params().spaceTop(lyxlayout->fill_top ?
464 VSpace(VSpace::VFILL)
465 : VSpace(VSpace::NONE));
466 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
467 VSpace(VSpace::VFILL)
468 : VSpace(VSpace::NONE));
469 if (lyxlayout->margintype == MARGIN_MANUAL)
470 par->setLabelWidthString(lyxlayout->labelstring());
473 } while (par != epar);
479 // set layout over selection and make a total rebreak of those paragraphs
480 void LyXText::setLayout(BufferView * bview, string const & layout)
482 LyXCursor tmpcursor = cursor; /* store the current cursor */
484 // if there is no selection just set the layout
485 // of the current paragraph */
486 if (!selection.set()) {
487 selection.start = cursor; // dummy selection
488 selection.end = cursor;
490 Paragraph * endpar = setLayout(bview, cursor, selection.start,
491 selection.end, layout);
492 redoParagraphs(bview, selection.start, endpar);
494 // we have to reset the selection, because the
495 // geometry could have changed
496 setCursor(bview, selection.start.par(),
497 selection.start.pos(), false);
498 selection.cursor = cursor;
499 setCursor(bview, selection.end.par(), selection.end.pos(), false);
500 updateCounters(bview);
503 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true);
507 // increment depth over selection and
508 // make a total rebreak of those paragraphs
509 void LyXText::incDepth(BufferView * bview)
511 // If there is no selection, just use the current paragraph
512 if (!selection.set()) {
513 selection.start = cursor; // dummy selection
514 selection.end = cursor;
517 // We end at the next paragraph with depth 0
518 Paragraph * endpar = selection.end.par()->next();
520 Paragraph * undoendpar = endpar;
522 if (endpar && endpar->getDepth()) {
523 while (endpar && endpar->getDepth()) {
524 endpar = endpar->next();
528 endpar = endpar->next(); // because of parindents etc.
531 setUndo(bview, Undo::EDIT,
532 selection.start.par(), undoendpar);
534 LyXCursor tmpcursor = cursor; // store the current cursor
536 // ok we have a selection. This is always between sel_start_cursor
537 // and sel_end cursor
538 cursor = selection.start;
541 // NOTE: you can't change the depth of a bibliography entry
542 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
543 Paragraph * prev = cursor.par()->previous();
546 if (cursor.par()->getDepth()
547 < prev->getMaxDepthAfter()) {
548 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
552 if (cursor.par() == selection.end.par())
554 cursor.par(cursor.par()->next());
557 redoParagraphs(bview, selection.start, endpar);
559 // we have to reset the selection, because the
560 // geometry could have changed
561 setCursor(bview, selection.start.par(), selection.start.pos());
562 selection.cursor = cursor;
563 setCursor(bview, selection.end.par(), selection.end.pos());
564 updateCounters(bview);
567 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
571 // decrement depth over selection and
572 // make a total rebreak of those paragraphs
573 void LyXText::decDepth(BufferView * bview)
575 // if there is no selection just set the layout
576 // of the current paragraph
577 if (!selection.set()) {
578 selection.start = cursor; // dummy selection
579 selection.end = cursor;
581 Paragraph * endpar = selection.end.par()->next();
582 Paragraph * undoendpar = endpar;
584 if (endpar && endpar->getDepth()) {
585 while (endpar && endpar->getDepth()) {
586 endpar = endpar->next();
590 endpar = endpar->next(); // because of parindents etc.
593 setUndo(bview, Undo::EDIT,
594 selection.start.par(), undoendpar);
596 LyXCursor tmpcursor = cursor; // store the current cursor
598 // ok we have a selection. This is always between sel_start_cursor
599 // and sel_end cursor
600 cursor = selection.start;
603 if (cursor.par()->params().depth()) {
604 cursor.par()->params()
605 .depth(cursor.par()->params().depth() - 1);
607 if (cursor.par() == selection.end.par()) {
610 cursor.par(cursor.par()->next());
613 redoParagraphs(bview, selection.start, endpar);
615 // we have to reset the selection, because the
616 // geometry could have changed
617 setCursor(bview, selection.start.par(),
618 selection.start.pos());
619 selection.cursor = cursor;
620 setCursor(bview, selection.end.par(), selection.end.pos());
621 updateCounters(bview);
624 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
628 // set font over selection and make a total rebreak of those paragraphs
629 void LyXText::setFont(BufferView * bview, LyXFont const & font, bool toggleall)
631 // if there is no selection just set the current_font
632 if (!selection.set()) {
633 // Determine basis font
635 if (cursor.pos() < cursor.par()->beginningOfMainBody()) {
636 layoutfont = getLabelFont(bview->buffer(),
639 layoutfont = getLayoutFont(bview->buffer(),
642 // Update current font
643 real_current_font.update(font,
644 bview->buffer()->params.language,
647 // Reduce to implicit settings
648 current_font = real_current_font;
649 current_font.reduce(layoutfont);
650 // And resolve it completely
651 real_current_font.realize(layoutfont);
656 LyXCursor tmpcursor = cursor; // store the current cursor
658 // ok we have a selection. This is always between sel_start_cursor
659 // and sel_end cursor
661 setUndo(bview, Undo::EDIT,
662 selection.start.par(), selection.end.par()->next());
664 cursor = selection.start;
665 while (cursor.par() != selection.end.par() ||
666 cursor.pos() < selection.end.pos())
668 if (cursor.pos() < cursor.par()->size()) {
669 // an open footnote should behave like a closed one
670 setCharFont(bview, cursor.par(), cursor.pos(),
672 cursor.pos(cursor.pos() + 1);
675 cursor.par(cursor.par()->next());
680 redoParagraphs(bview, selection.start, selection.end.par()->next());
682 // we have to reset the selection, because the
683 // geometry could have changed, but we keep
684 // it for user convenience
685 setCursor(bview, selection.start.par(), selection.start.pos());
686 selection.cursor = cursor;
687 setCursor(bview, selection.end.par(), selection.end.pos());
689 setCursor(bview, tmpcursor.par(), tmpcursor.pos(), true,
690 tmpcursor.boundary());
694 void LyXText::redoHeightOfParagraph(BufferView * bview, LyXCursor const & cur)
696 Row * tmprow = cur.row();
697 int y = cur.y() - tmprow->baseline();
699 setHeightOfRow(bview, tmprow);
701 while (tmprow->previous()
702 && tmprow->previous()->par() == tmprow->par()) {
703 tmprow = tmprow->previous();
704 y -= tmprow->height();
705 setHeightOfRow(bview, tmprow);
708 // we can set the refreshing parameters now
709 status(bview, LyXText::NEED_MORE_REFRESH);
711 refresh_row = tmprow;
712 setCursor(bview, cur.par(), cur.pos(), false, cursor.boundary());
716 void LyXText::redoDrawingOfParagraph(BufferView * bview, LyXCursor const & cur)
718 Row * tmprow = cur.row();
720 int y = cur.y() - tmprow->baseline();
721 setHeightOfRow(bview, tmprow);
723 while (tmprow->previous()
724 && tmprow->previous()->par() == tmprow->par()) {
725 tmprow = tmprow->previous();
726 y -= tmprow->height();
729 // we can set the refreshing parameters now
730 if (status_ == LyXText::UNCHANGED || y < refresh_y) {
732 refresh_row = tmprow;
734 status(bview, LyXText::NEED_MORE_REFRESH);
735 setCursor(bview, cur.par(), cur.pos());
739 // deletes and inserts again all paragaphs between the cursor
740 // and the specified par
741 // This function is needed after SetLayout and SetFont etc.
742 void LyXText::redoParagraphs(BufferView * bview, LyXCursor const & cur,
743 Paragraph const * endpar) const
746 Paragraph * tmppar = 0;
747 Paragraph * first_phys_par = 0;
749 Row * tmprow = cur.row();
751 int y = cur.y() - tmprow->baseline();
753 if (!tmprow->previous()) {
754 // a trick/hack for UNDO
755 // This is needed because in an UNDO/REDO we could have changed
756 // the ownerParagrah() so the paragraph inside the row is NOT
757 // my really first par anymore. Got it Lars ;) (Jug 20011206)
758 first_phys_par = ownerParagraph();
760 first_phys_par = tmprow->par();
761 while (tmprow->previous()
762 && tmprow->previous()->par() == first_phys_par)
764 tmprow = tmprow->previous();
765 y -= tmprow->height();
769 // we can set the refreshing parameters now
770 status(bview, LyXText::NEED_MORE_REFRESH);
772 refresh_row = tmprow->previous(); /* the real refresh row will
773 be deleted, so I store
777 tmppar = tmprow->next()->par();
780 while (tmprow->next() && tmppar != endpar) {
781 removeRow(tmprow->next());
782 if (tmprow->next()) {
783 tmppar = tmprow->next()->par();
789 // remove the first one
790 tmprow2 = tmprow; /* this is because tmprow->previous()
792 tmprow = tmprow->previous();
795 tmppar = first_phys_par;
799 insertParagraph(bview, tmppar, tmprow);
803 while (tmprow->next()
804 && tmprow->next()->par() == tmppar) {
805 tmprow = tmprow->next();
807 tmppar = tmppar->next();
809 } while (tmppar && tmppar != endpar);
811 // this is because of layout changes
813 refresh_y -= refresh_row->height();
814 setHeightOfRow(bview, refresh_row);
816 refresh_row = firstrow;
818 setHeightOfRow(bview, refresh_row);
821 if (tmprow && tmprow->next())
822 setHeightOfRow(bview, tmprow->next());
823 updateCounters(bview);
827 void LyXText::fullRebreak(BufferView * bview)
833 if (need_break_row) {
834 breakAgain(bview, need_break_row);
841 // important for the screen
844 // the cursor set functions have a special mechanism. When they
845 // realize, that you left an empty paragraph, they will delete it.
846 // They also delete the corresponding row
848 // need the selection cursor:
849 void LyXText::setSelection(BufferView * bview)
851 bool const lsel = selection.set();
853 if (!selection.set()) {
854 last_sel_cursor = selection.cursor;
855 selection.start = selection.cursor;
856 selection.end = selection.cursor;
861 // first the toggling area
862 if (cursor.y() < last_sel_cursor.y()
863 || (cursor.y() == last_sel_cursor.y()
864 && cursor.x() < last_sel_cursor.x())) {
865 toggle_end_cursor = last_sel_cursor;
866 toggle_cursor = cursor;
868 toggle_end_cursor = cursor;
869 toggle_cursor = last_sel_cursor;
872 last_sel_cursor = cursor;
874 // and now the whole selection
876 if (selection.cursor.par() == cursor.par())
877 if (selection.cursor.pos() < cursor.pos()) {
878 selection.end = cursor;
879 selection.start = selection.cursor;
881 selection.end = selection.cursor;
882 selection.start = cursor;
884 else if (selection.cursor.y() < cursor.y() ||
885 (selection.cursor.y() == cursor.y()
886 && selection.cursor.x() < cursor.x())) {
887 selection.end = cursor;
888 selection.start = selection.cursor;
891 selection.end = selection.cursor;
892 selection.start = cursor;
895 // a selection with no contents is not a selection
896 if (selection.start.par() == selection.end.par() &&
897 selection.start.pos() == selection.end.pos())
898 selection.set(false);
900 if (inset_owner && (selection.set() || lsel))
901 inset_owner->setUpdateStatus(bview, InsetText::SELECTION);
905 string const LyXText::selectionAsString(Buffer const * buffer,
908 if (!selection.set()) return string();
910 // should be const ...
911 Paragraph * startpar(selection.start.par());
912 Paragraph * endpar(selection.end.par());
913 pos_type const startpos(selection.start.pos());
914 pos_type const endpos(selection.end.pos());
916 if (startpar == endpar) {
917 return startpar->asString(buffer, startpos, endpos, label);
922 // First paragraph in selection
923 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
925 // The paragraphs in between (if any)
926 LyXCursor tmpcur(selection.start);
927 tmpcur.par(tmpcur.par()->next());
928 while (tmpcur.par() != endpar) {
929 result += tmpcur.par()->asString(buffer, 0,
930 tmpcur.par()->size(),
932 tmpcur.par(tmpcur.par()->next());
935 // Last paragraph in selection
936 result += endpar->asString(buffer, 0, endpos, label);
942 void LyXText::clearSelection() const
944 selection.set(false);
945 selection.mark(false);
946 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
947 // reset this in the bv_owner!
948 if (bv_owner && bv_owner->text)
949 bv_owner->text->xsel_cache.set(false);
953 void LyXText::cursorHome(BufferView * bview) const
955 setCursor(bview, cursor.par(), cursor.row()->pos());
959 void LyXText::cursorEnd(BufferView * bview) const
961 if (!cursor.row()->next()
962 || cursor.row()->next()->par() != cursor.row()->par()) {
963 setCursor(bview, cursor.par(), cursor.row()->lastPos() + 1);
965 if (!cursor.par()->empty() &&
966 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
967 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
968 setCursor(bview, cursor.par(), cursor.row()->lastPos());
970 setCursor(bview,cursor.par(),
971 cursor.row()->lastPos() + 1);
977 void LyXText::cursorTop(BufferView * bview) const
979 while (cursor.par()->previous())
980 cursor.par(cursor.par()->previous());
981 setCursor(bview, cursor.par(), 0);
985 void LyXText::cursorBottom(BufferView * bview) const
987 while (cursor.par()->next())
988 cursor.par(cursor.par()->next());
989 setCursor(bview, cursor.par(), cursor.par()->size());
993 void LyXText::toggleFree(BufferView * bview,
994 LyXFont const & font, bool toggleall)
996 // If the mask is completely neutral, tell user
997 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
998 // Could only happen with user style
999 bview->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
1003 // Try implicit word selection
1004 // If there is a change in the language the implicit word selection
1006 LyXCursor resetCursor = cursor;
1007 bool implicitSelection = (font.language() == ignore_language
1008 && font.number() == LyXFont::IGNORE)
1009 ? selectWordWhenUnderCursor(bview, WHOLE_WORD_STRICT) : false;
1012 setFont(bview, font, toggleall);
1014 // Implicit selections are cleared afterwards
1015 //and cursor is set to the original position.
1016 if (implicitSelection) {
1018 cursor = resetCursor;
1019 setCursor(bview, cursor.par(), cursor.pos());
1020 selection.cursor = cursor;
1023 inset_owner->setUpdateStatus(bview, InsetText::CURSOR_PAR);
1027 string LyXText::getStringToIndex(BufferView * bview)
1029 // Try implicit word selection
1030 // If there is a change in the language the implicit word selection
1032 LyXCursor const reset_cursor = cursor;
1033 bool const implicitSelection = selectWordWhenUnderCursor(bview, PREVIOUS_WORD);
1036 if (!selection.set())
1037 bview->owner()->message(_("Nothing to index!"));
1038 else if (selection.start.par() != selection.end.par())
1039 bview->owner()->message(_("Cannot index more than one paragraph!"));
1041 idxstring = selectionAsString(bview->buffer(), false);
1043 // Reset cursors to their original position.
1044 cursor = reset_cursor;
1045 setCursor(bview, cursor.par(), cursor.pos());
1046 selection.cursor = cursor;
1048 // Clear the implicit selection.
1049 if (implicitSelection)
1056 // the DTP switches for paragraphs. LyX will store them in the first
1057 // physicla paragraph. When a paragraph is broken, the top settings rest,
1058 // the bottom settings are given to the new one. So I can make shure,
1059 // they do not duplicate themself and you cannnot make dirty things with
1062 void LyXText::setParagraph(BufferView * bview,
1063 bool line_top, bool line_bottom,
1064 bool pagebreak_top, bool pagebreak_bottom,
1065 VSpace const & space_top,
1066 VSpace const & space_bottom,
1067 Spacing const & spacing,
1069 string labelwidthstring,
1072 LyXCursor tmpcursor = cursor;
1073 if (!selection.set()) {
1074 selection.start = cursor;
1075 selection.end = cursor;
1078 // make sure that the depth behind the selection are restored, too
1079 Paragraph * endpar = selection.end.par()->next();
1080 Paragraph * undoendpar = endpar;
1082 if (endpar && endpar->getDepth()) {
1083 while (endpar && endpar->getDepth()) {
1084 endpar = endpar->next();
1085 undoendpar = endpar;
1089 // because of parindents etc.
1090 endpar = endpar->next();
1093 setUndo(bview, Undo::EDIT, selection.start.par(), undoendpar);
1096 Paragraph * tmppar = selection.end.par();
1098 while (tmppar != selection.start.par()->previous()) {
1099 setCursor(bview, tmppar, 0);
1100 status(bview, LyXText::NEED_MORE_REFRESH);
1101 refresh_row = cursor.row();
1102 refresh_y = cursor.y() - cursor.row()->baseline();
1103 cursor.par()->params().lineTop(line_top);
1104 cursor.par()->params().lineBottom(line_bottom);
1105 cursor.par()->params().pagebreakTop(pagebreak_top);
1106 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1107 cursor.par()->params().spaceTop(space_top);
1108 cursor.par()->params().spaceBottom(space_bottom);
1109 cursor.par()->params().spacing(spacing);
1110 // does the layout allow the new alignment?
1111 LyXLayout_ptr const & layout = cursor.par()->layout();
1113 if (align == LYX_ALIGN_LAYOUT)
1114 align = layout->align;
1115 if (align & layout->alignpossible) {
1116 if (align == layout->align)
1117 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1119 cursor.par()->params().align(align);
1121 cursor.par()->setLabelWidthString(labelwidthstring);
1122 cursor.par()->params().noindent(noindent);
1123 tmppar = cursor.par()->previous();
1126 redoParagraphs(bview, selection.start, endpar);
1129 setCursor(bview, selection.start.par(), selection.start.pos());
1130 selection.cursor = cursor;
1131 setCursor(bview, selection.end.par(), selection.end.pos());
1132 setSelection(bview);
1133 setCursor(bview, tmpcursor.par(), tmpcursor.pos());
1135 bview->updateInset(inset_owner, true);
1139 // set the counter of a paragraph. This includes the labels
1140 void LyXText::setCounter(Buffer const * buf, Paragraph * par) const
1142 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1143 LyXLayout_ptr const & layout = par->layout();
1145 if (par->previous()) {
1147 par->params().appendix(par->previous()->params().appendix());
1148 if (!par->params().appendix() && par->params().startOfAppendix()) {
1149 par->params().appendix(true);
1150 textclass.counters().reset();
1152 par->enumdepth = par->previous()->enumdepth;
1153 par->itemdepth = par->previous()->itemdepth;
1155 par->params().appendix(par->params().startOfAppendix());
1160 /* Maybe we have to increment the enumeration depth.
1161 * BUT, enumeration in a footnote is considered in isolation from its
1162 * surrounding paragraph so don't increment if this is the
1163 * first line of the footnote
1164 * AND, bibliographies can't have their depth changed ie. they
1165 * are always of depth 0
1168 && par->previous()->getDepth() < par->getDepth()
1169 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1170 && par->enumdepth < 3
1171 && layout->labeltype != LABEL_BIBLIO) {
1175 // Maybe we have to decrement the enumeration depth, see note above
1177 && par->previous()->getDepth() > par->getDepth()
1178 && layout->labeltype != LABEL_BIBLIO) {
1179 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1182 if (!par->params().labelString().empty()) {
1183 par->params().labelString(string());
1186 if (layout->margintype == MARGIN_MANUAL) {
1187 if (par->params().labelWidthString().empty()) {
1188 par->setLabelWidthString(layout->labelstring());
1191 par->setLabelWidthString(string());
1194 // is it a layout that has an automatic label?
1195 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1196 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1200 if (i >= 0 && i <= buf->params.secnumdepth) {
1204 textclass.counters().step(layout->latexname());
1206 // Is there a label? Useful for Chapter layout
1207 if (!par->params().appendix()) {
1208 s << layout->labelstring();
1210 s << layout->labelstring_appendix();
1213 // Use of an integer is here less than elegant. For now.
1214 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1215 if (!par->params().appendix()) {
1216 numbertype = "sectioning";
1218 numbertype = "appendix";
1219 if (par->isRightToLeftPar(buf->params))
1220 langtype = "hebrew";
1225 s << textclass.counters()
1226 .numberLabel(layout->latexname(),
1227 numbertype, langtype, head);
1229 par->params().labelString(STRCONV(s.str()));
1231 // reset enum counters
1232 textclass.counters().reset("enum");
1233 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1234 textclass.counters().reset("enum");
1235 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1237 // Yes I know this is a really, really! bad solution
1239 string enumcounter("enum");
1241 switch (par->enumdepth) {
1250 enumcounter += "iv";
1253 // not a valid enumdepth...
1257 textclass.counters().step(enumcounter);
1259 s << textclass.counters()
1260 .numberLabel(enumcounter, "enumeration");
1261 par->params().labelString(STRCONV(s.str()));
1263 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1264 textclass.counters().step("bibitem");
1265 int number = textclass.counters().value("bibitem");
1266 if (par->bibitem()) {
1267 par->bibitem()->setCounter(number);
1268 par->params().labelString(layout->labelstring());
1270 // In biblio should't be following counters but...
1272 string s = layout->labelstring();
1274 // the caption hack:
1275 if (layout->labeltype == LABEL_SENSITIVE) {
1276 Paragraph * tmppar = par;
1279 while (tmppar && tmppar->inInset()
1280 // the single '=' is intended below
1281 && (in = tmppar->inInset()->owner())) {
1282 if (in->lyxCode() == Inset::FLOAT_CODE ||
1283 in->lyxCode() == Inset::WRAP_CODE) {
1287 tmppar = in->parOwner();
1293 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1295 textclass.counters().step(fl.type());
1297 // Doesn't work... yet.
1298 #warning use boost.format
1299 #if USE_BOOST_FORMAT
1300 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1301 // s << boost::format(_("%1$s %1$d:")
1303 // % buf->counters().value(fl.name());
1306 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1307 o << fl.name() << " #:";
1308 s = STRCONV(o.str());
1311 // par->SetLayout(0);
1312 // s = layout->labelstring;
1313 s = _("Senseless: ");
1316 par->params().labelString(s);
1318 // reset the enumeration counter. They are always reset
1319 // when there is any other layout between
1320 // Just fall-through between the cases so that all
1321 // enum counters deeper than enumdepth is also reset.
1322 switch (par->enumdepth) {
1324 textclass.counters().reset("enumi");
1326 textclass.counters().reset("enumii");
1328 textclass.counters().reset("enumiii");
1330 textclass.counters().reset("enumiv");
1336 // Updates all counters. Paragraphs with changed label string will be rebroken
1337 void LyXText::updateCounters(BufferView * bview) const
1339 Row * row = firstrow;
1340 Paragraph * par = row->par();
1342 // CHECK if this is really needed. (Lgb)
1343 bview->buffer()->params.getLyXTextClass().counters().reset();
1346 while (row->par() != par)
1349 string const oldLabel = par->params().labelString();
1351 // setCounter can potentially change the labelString.
1352 setCounter(bview->buffer(), par);
1354 string const & newLabel = par->params().labelString();
1356 if (oldLabel.empty() && !newLabel.empty()) {
1357 removeParagraph(row);
1358 appendParagraph(bview, row);
1366 void LyXText::insertInset(BufferView * bview, Inset * inset)
1368 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1370 setUndo(bview, Undo::FINISH, cursor.par(), cursor.par()->next());
1372 cursor.par()->insertInset(cursor.pos(), inset);
1373 // Just to rebreak and refresh correctly.
1374 // The character will not be inserted a second time
1375 insertChar(bview, Paragraph::META_INSET);
1376 // If we enter a highly editable inset the cursor should be to before
1377 // the inset. This couldn't happen before as Undo was not handled inside
1378 // inset now after the Undo LyX tries to call inset->Edit(...) again
1379 // and cannot do this as the cursor is behind the inset and GetInset
1380 // does not return the inset!
1381 if (isHighlyEditableInset(inset)) {
1382 cursorLeft(bview, true);
1388 void LyXText::copyEnvironmentType()
1390 copylayouttype = cursor.par()->layout()->name();
1394 void LyXText::pasteEnvironmentType(BufferView * bview)
1396 // do nothing if there has been no previous copyEnvironmentType()
1397 if (!copylayouttype.empty())
1398 setLayout(bview, copylayouttype);
1402 void LyXText::cutSelection(BufferView * bview, bool doclear, bool realcut)
1404 // Stuff what we got on the clipboard. Even if there is no selection.
1406 // There is a problem with having the stuffing here in that the
1407 // larger the selection the slower LyX will get. This can be
1408 // solved by running the line below only when the selection has
1409 // finished. The solution used currently just works, to make it
1410 // faster we need to be more clever and probably also have more
1411 // calls to stuffClipboard. (Lgb)
1412 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1414 // This doesn't make sense, if there is no selection
1415 if (!selection.set())
1418 // OK, we have a selection. This is always between selection.start
1419 // and selection.end
1421 // make sure that the depth behind the selection are restored, too
1422 Paragraph * endpar = selection.end.par()->next();
1423 Paragraph * undoendpar = endpar;
1425 if (endpar && endpar->getDepth()) {
1426 while (endpar && endpar->getDepth()) {
1427 endpar = endpar->next();
1428 undoendpar = endpar;
1430 } else if (endpar) {
1431 endpar = endpar->next(); // because of parindents etc.
1434 setUndo(bview, Undo::DELETE,
1435 selection.start.par(), undoendpar);
1437 // there are two cases: cut only within one paragraph or
1438 // more than one paragraph
1439 if (selection.start.par() == selection.end.par()) {
1440 // only within one paragraph
1441 endpar = selection.end.par();
1442 int pos = selection.end.pos();
1443 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1444 selection.start.pos(), pos,
1445 bview->buffer()->params.textclass,
1447 selection.end.pos(pos);
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,
1456 selection.end.par(endpar);
1457 selection.end.pos(pos);
1458 cursor.pos(selection.end.pos());
1460 endpar = endpar->next();
1462 // sometimes necessary
1464 selection.start.par()->stripLeadingSpaces();
1466 redoParagraphs(bview, selection.start, endpar);
1468 // cutSelection can invalidate the cursor so we need to set
1470 // we prefer the end for when tracking changes
1471 cursor = selection.end;
1473 // need a valid cursor. (Lgb)
1476 setCursor(bview, cursor.par(), cursor.pos());
1477 selection.cursor = cursor;
1478 updateCounters(bview);
1482 void LyXText::copySelection(BufferView * bview)
1484 // stuff the selection onto the X clipboard, from an explicit copy request
1485 bview->stuffClipboard(selectionAsString(bview->buffer(), true));
1487 // this doesnt make sense, if there is no selection
1488 if (!selection.set())
1491 // ok we have a selection. This is always between selection.start
1492 // and sel_end cursor
1494 // copy behind a space if there is one
1495 while (selection.start.par()->size() > selection.start.pos()
1496 && selection.start.par()->isLineSeparator(selection.start.pos())
1497 && (selection.start.par() != selection.end.par()
1498 || selection.start.pos() < selection.end.pos()))
1499 selection.start.pos(selection.start.pos() + 1);
1501 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1502 selection.start.pos(), selection.end.pos(),
1503 bview->buffer()->params.textclass);
1507 void LyXText::pasteSelection(BufferView * bview)
1509 // this does not make sense, if there is nothing to paste
1510 if (!CutAndPaste::checkPastePossible(cursor.par()))
1513 setUndo(bview, Undo::INSERT,
1514 cursor.par(), cursor.par()->next());
1517 Paragraph * actpar = cursor.par();
1518 int pos = cursor.pos();
1520 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1521 bview->buffer()->params.textclass);
1523 redoParagraphs(bview, cursor, endpar);
1525 setCursor(bview, cursor.par(), cursor.pos());
1528 selection.cursor = cursor;
1529 setCursor(bview, actpar, pos);
1530 setSelection(bview);
1531 updateCounters(bview);
1535 void LyXText::setSelectionRange(BufferView * bview, lyx::pos_type length)
1540 selection.cursor = cursor;
1543 setSelection(bview);
1547 // simple replacing. The font of the first selected character is used
1548 void LyXText::replaceSelectionWithString(BufferView * bview,
1551 setCursorParUndo(bview);
1554 if (!selection.set()) { // create a dummy selection
1555 selection.end = cursor;
1556 selection.start = cursor;
1559 // Get font setting before we cut
1560 pos_type pos = selection.end.pos();
1561 LyXFont const font = selection.start.par()
1562 ->getFontSettings(bview->buffer()->params,
1563 selection.start.pos());
1565 // Insert the new string
1566 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1567 selection.end.par()->insertChar(pos, (*cit), font);
1571 // Cut the selection
1572 cutSelection(bview, true, false);
1578 // needed to insert the selection
1579 void LyXText::insertStringAsLines(BufferView * bview, string const & str)
1581 Paragraph * par = cursor.par();
1582 pos_type pos = cursor.pos();
1583 Paragraph * endpar = cursor.par()->next();
1585 setCursorParUndo(bview);
1587 // only to be sure, should not be neccessary
1590 bview->buffer()->insertStringAsLines(par, pos, current_font, str);
1592 redoParagraphs(bview, cursor, endpar);
1593 setCursor(bview, cursor.par(), cursor.pos());
1594 selection.cursor = cursor;
1595 setCursor(bview, par, pos);
1596 setSelection(bview);
1600 // turns double-CR to single CR, others where converted into one
1601 // blank. Then InsertStringAsLines is called
1602 void LyXText::insertStringAsParagraphs(BufferView * bview, string const & str)
1604 string linestr(str);
1605 bool newline_inserted = false;
1606 for (string::size_type i = 0; i < linestr.length(); ++i) {
1607 if (linestr[i] == '\n') {
1608 if (newline_inserted) {
1609 // we know that \r will be ignored by
1610 // InsertStringA. Of course, it is a dirty
1611 // trick, but it works...
1612 linestr[i - 1] = '\r';
1616 newline_inserted = true;
1618 } else if (IsPrintable(linestr[i])) {
1619 newline_inserted = false;
1622 insertStringAsLines(bview, linestr);
1626 void LyXText::checkParagraph(BufferView * bview, Paragraph * par,
1629 LyXCursor tmpcursor;
1633 Row * row = getRow(par, pos, y);
1635 // is there a break one row above
1636 if (row->previous() && row->previous()->par() == row->par()) {
1637 z = nextBreakPoint(bview, row->previous(), workWidth(bview));
1638 if (z >= row->pos()) {
1639 // set the dimensions of the row above
1640 y -= row->previous()->height();
1642 refresh_row = row->previous();
1643 status(bview, LyXText::NEED_MORE_REFRESH);
1645 breakAgain(bview, row->previous());
1647 // set the cursor again. Otherwise
1648 // dangling pointers are possible
1649 setCursor(bview, cursor.par(), cursor.pos(),
1650 false, cursor.boundary());
1651 selection.cursor = cursor;
1656 int const tmpheight = row->height();
1657 pos_type const tmplast = row->lastPos();
1661 breakAgain(bview, row);
1662 if (row->height() == tmpheight && row->lastPos() == tmplast)
1663 status(bview, LyXText::NEED_VERY_LITTLE_REFRESH);
1665 status(bview, LyXText::NEED_MORE_REFRESH);
1667 // check the special right address boxes
1668 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1675 redoDrawingOfParagraph(bview, tmpcursor);
1678 // set the cursor again. Otherwise dangling pointers are possible
1679 // also set the selection
1681 if (selection.set()) {
1683 setCursorIntern(bview, selection.cursor.par(), selection.cursor.pos(),
1684 false, selection.cursor.boundary());
1685 selection.cursor = cursor;
1686 setCursorIntern(bview, selection.start.par(),
1687 selection.start.pos(),
1688 false, selection.start.boundary());
1689 selection.start = cursor;
1690 setCursorIntern(bview, selection.end.par(),
1691 selection.end.pos(),
1692 false, selection.end.boundary());
1693 selection.end = cursor;
1694 setCursorIntern(bview, last_sel_cursor.par(),
1695 last_sel_cursor.pos(),
1696 false, last_sel_cursor.boundary());
1697 last_sel_cursor = cursor;
1700 setCursorIntern(bview, cursor.par(), cursor.pos(),
1701 false, cursor.boundary());
1705 // returns false if inset wasn't found
1706 bool LyXText::updateInset(BufferView * bview, Inset * inset)
1708 // first check the current paragraph
1709 int pos = cursor.par()->getPositionOfInset(inset);
1711 checkParagraph(bview, cursor.par(), pos);
1715 // check every paragraph
1717 Paragraph * par = ownerParagraph();
1719 pos = par->getPositionOfInset(inset);
1721 checkParagraph(bview, par, pos);
1731 bool LyXText::setCursor(BufferView * bview, Paragraph * par,
1733 bool setfont, bool boundary) const
1735 LyXCursor old_cursor = cursor;
1736 setCursorIntern(bview, par, pos, setfont, boundary);
1737 return deleteEmptyParagraphMechanism(bview, old_cursor);
1741 void LyXText::setCursor(BufferView * bview, LyXCursor & cur, Paragraph * par,
1742 pos_type pos, bool boundary) const
1749 cur.boundary(boundary);
1751 // get the cursor y position in text
1753 Row * row = getRow(par, pos, y);
1754 Row * old_row = row;
1756 // if we are before the first char of this row and are still in the
1757 // same paragraph and there is a previous row then put the cursor on
1758 // the end of the previous row
1759 cur.iy(y + row->baseline());
1761 if (row->previous() && pos &&
1762 row->previous()->par() == row->par() &&
1763 par->getChar(pos) == Paragraph::META_INSET &&
1764 (ins=par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1766 row = row->previous();
1771 // y is now the beginning of the cursor row
1772 y += row->baseline();
1773 // y is now the cursor baseline
1776 pos_type last = old_row->lastPrintablePos();
1778 // None of these should happen, but we're scaredy-cats
1779 if (pos > par->size()) {
1782 } else if (pos > last + 1) {
1783 // This shouldn't happen.
1786 } else if (pos < row->pos()) {
1791 // now get the cursors x position
1792 float x = getCursorX(bview, row, pos, last, boundary);
1795 if (old_row != row) {
1796 x = getCursorX(bview, old_row, pos, last, boundary);
1803 float LyXText::getCursorX(BufferView * bview, Row * row,
1804 pos_type pos, pos_type last, bool boundary) const
1806 pos_type cursor_vpos = 0;
1808 float fill_separator;
1810 float fill_label_hfill;
1811 // This call HAS to be here because of the BidiTables!!!
1812 prepareToPrint(bview, row, x, fill_separator, fill_hfill,
1815 if (last < row->pos())
1816 cursor_vpos = row->pos();
1817 else if (pos > last && !boundary)
1818 cursor_vpos = (row->par()->isRightToLeftPar(bview->buffer()->params))
1819 ? row->pos() : last + 1;
1820 else if (pos > row->pos() &&
1821 (pos > last || boundary))
1822 /// Place cursor after char at (logical) position pos - 1
1823 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1824 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1826 /// Place cursor before char at (logical) position pos
1827 cursor_vpos = (bidi_level(pos) % 2 == 0)
1828 ? log2vis(pos) : log2vis(pos) + 1;
1830 pos_type main_body = row->par()->beginningOfMainBody();
1831 if ((main_body > 0) &&
1832 ((main_body-1 > last) ||
1833 !row->par()->isLineSeparator(main_body-1)))
1836 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1837 pos_type pos = vis2log(vpos);
1838 if (main_body > 0 && pos == main_body - 1) {
1839 x += fill_label_hfill +
1840 font_metrics::width(
1841 row->par()->layout()->labelsep,
1842 getLabelFont(bview->buffer(),
1844 if (row->par()->isLineSeparator(main_body - 1))
1845 x -= singleWidth(bview,
1846 row->par(), main_body - 1);
1848 if (row->hfillExpansion(pos)) {
1849 x += singleWidth(bview, row->par(), pos);
1850 if (pos >= main_body)
1853 x += fill_label_hfill;
1854 } else if (row->par()->isSeparator(pos)) {
1855 x += singleWidth(bview, row->par(), pos);
1856 if (pos >= main_body)
1857 x += fill_separator;
1859 x += singleWidth(bview, row->par(), pos);
1865 void LyXText::setCursorIntern(BufferView * bview, Paragraph * par,
1866 pos_type pos, bool setfont, bool boundary) const
1868 InsetText * it = static_cast<InsetText *>(par->inInset());
1870 if (it != inset_owner) {
1871 lyxerr[Debug::INSETS] << "InsetText is " << it
1873 << "inset_owner is "
1874 << inset_owner << endl;
1875 #ifdef WITH_WARNINGS
1876 #warning I believe this code is wrong. (Lgb)
1877 #warning Jürgen, have a look at this. (Lgb)
1878 #warning Hmmm, I guess you are right but we
1879 #warning should verify when this is needed
1881 // Jürgen, would you like to have a look?
1882 // I guess we need to move the outer cursor
1883 // and open and lock the inset (bla bla bla)
1884 // stuff I don't know... so can you have a look?
1886 // I moved the lyxerr stuff in here so we can see if
1887 // this is actually really needed and where!
1889 // it->getLyXText(bview)->setCursorIntern(bview, par, pos, setfont, boundary);
1894 setCursor(bview, cursor, par, pos, boundary);
1896 setCurrentFont(bview);
1900 void LyXText::setCurrentFont(BufferView * bview) const
1902 pos_type pos = cursor.pos();
1903 if (cursor.boundary() && pos > 0)
1907 if (pos == cursor.par()->size())
1909 else // potentional bug... BUG (Lgb)
1910 if (cursor.par()->isSeparator(pos)) {
1911 if (pos > cursor.row()->pos() &&
1912 bidi_level(pos) % 2 ==
1913 bidi_level(pos - 1) % 2)
1915 else if (pos + 1 < cursor.par()->size())
1921 cursor.par()->getFontSettings(bview->buffer()->params, pos);
1922 real_current_font = getFont(bview->buffer(), cursor.par(), pos);
1924 if (cursor.pos() == cursor.par()->size() &&
1925 isBoundary(bview->buffer(), cursor.par(), cursor.pos()) &&
1926 !cursor.boundary()) {
1927 Language const * lang =
1928 cursor.par()->getParLanguage(bview->buffer()->params);
1929 current_font.setLanguage(lang);
1930 current_font.setNumber(LyXFont::OFF);
1931 real_current_font.setLanguage(lang);
1932 real_current_font.setNumber(LyXFont::OFF);
1937 void LyXText::setCursorFromCoordinates(BufferView * bview, int x, int y) const
1939 LyXCursor old_cursor = cursor;
1941 setCursorFromCoordinates(bview, cursor, x, y);
1942 setCurrentFont(bview);
1943 deleteEmptyParagraphMechanism(bview, old_cursor);
1950 * return true if the cursor given is at the end of a row,
1951 * and the next row is filled by an inset that spans an entire
1954 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
1957 Row const & next = *row.next();
1959 if (next.pos() != cur.pos() || next.par() != cur.par())
1961 if (!cur.par()->isInset(cur.pos()))
1963 Inset const * inset = cur.par()->getInset(cur.pos());
1964 if (inset->needFullRow() || inset->display())
1971 void LyXText::setCursorFromCoordinates(BufferView * bview, LyXCursor & cur,
1974 // Get the row first.
1976 Row * row = getRowNearY(y);
1978 pos_type const column = getColumnNearX(bview, row, x, bound);
1979 cur.par(row->par());
1980 cur.pos(row->pos() + column);
1982 cur.y(y + row->baseline());
1985 if (beforeFullRowInset(*row, cur)) {
1986 pos_type last = row->lastPrintablePos();
1987 float x = getCursorX(bview, row->next(), cur.pos(), last, bound);
1989 cur.iy(y + row->height() + row->next()->baseline());
1990 cur.irow(row->next());
1996 cur.boundary(bound);
2000 void LyXText::cursorLeft(BufferView * bview, bool internal) const
2002 if (cursor.pos() > 0) {
2003 bool boundary = cursor.boundary();
2004 setCursor(bview, cursor.par(), cursor.pos() - 1, true, false);
2005 if (!internal && !boundary &&
2006 isBoundary(bview->buffer(), cursor.par(), cursor.pos() + 1))
2007 setCursor(bview, cursor.par(), cursor.pos() + 1, true, true);
2008 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2009 Paragraph * par = cursor.par()->previous();
2010 setCursor(bview, par, par->size());
2015 void LyXText::cursorRight(BufferView * bview, bool internal) const
2017 if (!internal && cursor.boundary() &&
2018 !cursor.par()->isNewline(cursor.pos()))
2019 setCursor(bview, cursor.par(), cursor.pos(), true, false);
2020 else if (cursor.pos() < cursor.par()->size()) {
2021 setCursor(bview, cursor.par(), cursor.pos() + 1, true, false);
2023 isBoundary(bview->buffer(), cursor.par(), cursor.pos()))
2024 setCursor(bview, cursor.par(), cursor.pos(), true, true);
2025 } else if (cursor.par()->next())
2026 setCursor(bview, cursor.par()->next(), 0);
2030 void LyXText::cursorUp(BufferView * bview, bool selecting) const
2033 int x = cursor.x_fix();
2034 int y = cursor.y() - cursor.row()->baseline() - 1;
2035 setCursorFromCoordinates(bview, x, y);
2037 int y1 = cursor.iy() - first_y;
2040 Inset * inset_hit = checkInsetHit(bview, x, y1);
2041 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2042 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2046 setCursorFromCoordinates(bview, cursor.x_fix(),
2047 cursor.y() - cursor.row()->baseline() - 1);
2052 void LyXText::cursorDown(BufferView * bview, bool selecting) const
2055 int x = cursor.x_fix();
2056 int y = cursor.y() - cursor.row()->baseline() +
2057 cursor.row()->height() + 1;
2058 setCursorFromCoordinates(bview, x, y);
2059 if (!selecting && cursor.row() == cursor.irow()) {
2060 int y1 = cursor.iy() - first_y;
2063 Inset * inset_hit = checkInsetHit(bview, x, y1);
2064 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2065 inset_hit->edit(bview, x, y - (y2 - y1), mouse_button::none);
2069 setCursorFromCoordinates(bview, cursor.x_fix(),
2070 cursor.y() - cursor.row()->baseline()
2071 + cursor.row()->height() + 1);
2076 void LyXText::cursorUpParagraph(BufferView * bview) const
2078 if (cursor.pos() > 0) {
2079 setCursor(bview, cursor.par(), 0);
2081 else if (cursor.par()->previous()) {
2082 setCursor(bview, cursor.par()->previous(), 0);
2087 void LyXText::cursorDownParagraph(BufferView * bview) const
2089 if (cursor.par()->next()) {
2090 setCursor(bview, cursor.par()->next(), 0);
2092 setCursor(bview, cursor.par(), cursor.par()->size());
2096 // fix the cursor `cur' after a characters has been deleted at `where'
2097 // position. Called by deleteEmptyParagraphMechanism
2098 void LyXText::fixCursorAfterDelete(BufferView * bview,
2100 LyXCursor const & where) const
2102 // if cursor is not in the paragraph where the delete occured,
2104 if (cur.par() != where.par())
2107 // if cursor position is after the place where the delete occured,
2109 if (cur.pos() > where.pos())
2110 cur.pos(cur.pos()-1);
2112 // check also if we don't want to set the cursor on a spot behind the
2113 // pagragraph because we erased the last character.
2114 if (cur.pos() > cur.par()->size())
2115 cur.pos(cur.par()->size());
2117 // recompute row et al. for this cursor
2118 setCursor(bview, cur, cur.par(), cur.pos(), cur.boundary());
2122 bool LyXText::deleteEmptyParagraphMechanism(BufferView * bview,
2123 LyXCursor const & old_cursor) const
2125 // Would be wrong to delete anything if we have a selection.
2126 if (selection.set())
2129 // We allow all kinds of "mumbo-jumbo" when freespacing.
2130 if (old_cursor.par()->layout()->free_spacing
2131 || old_cursor.par()->isFreeSpacing()) {
2135 /* Ok I'll put some comments here about what is missing.
2136 I have fixed BackSpace (and thus Delete) to not delete
2137 double-spaces automagically. I have also changed Cut,
2138 Copy and Paste to hopefully do some sensible things.
2139 There are still some small problems that can lead to
2140 double spaces stored in the document file or space at
2141 the beginning of paragraphs. This happens if you have
2142 the cursor betwenn to spaces and then save. Or if you
2143 cut and paste and the selection have a space at the
2144 beginning and then save right after the paste. I am
2145 sure none of these are very hard to fix, but I will
2146 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2147 that I can get some feedback. (Lgb)
2150 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2151 // delete the LineSeparator.
2154 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2155 // delete the LineSeparator.
2158 // If the pos around the old_cursor were spaces, delete one of them.
2159 if (old_cursor.par() != cursor.par()
2160 || old_cursor.pos() != cursor.pos()) {
2161 // Only if the cursor has really moved
2163 if (old_cursor.pos() > 0
2164 && old_cursor.pos() < old_cursor.par()->size()
2165 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2166 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2167 old_cursor.par()->erase(old_cursor.pos() - 1);
2168 redoParagraphs(bview, old_cursor, old_cursor.par()->next());
2170 #ifdef WITH_WARNINGS
2171 #warning This will not work anymore when we have multiple views of the same buffer
2172 // In this case, we will have to correct also the cursors held by
2173 // other bufferviews. It will probably be easier to do that in a more
2174 // automated way in LyXCursor code. (JMarc 26/09/2001)
2176 // correct all cursors held by the LyXText
2177 fixCursorAfterDelete(bview, cursor, old_cursor);
2178 fixCursorAfterDelete(bview, selection.cursor,
2180 fixCursorAfterDelete(bview, selection.start,
2182 fixCursorAfterDelete(bview, selection.end, old_cursor);
2183 fixCursorAfterDelete(bview, last_sel_cursor,
2185 fixCursorAfterDelete(bview, toggle_cursor, old_cursor);
2186 fixCursorAfterDelete(bview, toggle_end_cursor,
2192 // don't delete anything if this is the ONLY paragraph!
2193 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2196 // Do not delete empty paragraphs with keepempty set.
2197 if (old_cursor.par()->layout()->keepempty)
2200 // only do our magic if we changed paragraph
2201 if (old_cursor.par() == cursor.par())
2204 // record if we have deleted a paragraph
2205 // we can't possibly have deleted a paragraph before this point
2206 bool deleted = false;
2208 if ((old_cursor.par()->empty()
2209 || (old_cursor.par()->size() == 1
2210 && old_cursor.par()->isLineSeparator(0)))) {
2211 // ok, we will delete anything
2212 LyXCursor tmpcursor;
2214 // make sure that you do not delete any environments
2215 status(bview, LyXText::NEED_MORE_REFRESH);
2218 if (old_cursor.row()->previous()) {
2219 refresh_row = old_cursor.row()->previous();
2220 refresh_y = old_cursor.y() - old_cursor.row()->baseline() - refresh_row->height();
2222 cursor = old_cursor; // that undo can restore the right cursor position
2223 Paragraph * endpar = old_cursor.par()->next();
2224 if (endpar && endpar->getDepth()) {
2225 while (endpar && endpar->getDepth()) {
2226 endpar = endpar->next();
2229 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2233 removeRow(old_cursor.row());
2234 if (ownerParagraph() == old_cursor.par()) {
2235 ownerParagraph(ownerParagraph()->next());
2238 delete old_cursor.par();
2240 /* Breakagain the next par. Needed because of
2241 * the parindent that can occur or dissappear.
2242 * The next row can change its height, if
2243 * there is another layout before */
2244 if (refresh_row->next()) {
2245 breakAgain(bview, refresh_row->next());
2246 updateCounters(bview);
2248 setHeightOfRow(bview, refresh_row);
2250 refresh_row = old_cursor.row()->next();
2251 refresh_y = old_cursor.y() - old_cursor.row()->baseline();
2254 cursor = old_cursor; // that undo can restore the right cursor position
2255 Paragraph * endpar = old_cursor.par()->next();
2256 if (endpar && endpar->getDepth()) {
2257 while (endpar && endpar->getDepth()) {
2258 endpar = endpar->next();
2261 setUndo(bview, Undo::DELETE, old_cursor.par(), endpar);
2265 removeRow(old_cursor.row());
2267 if (ownerParagraph() == old_cursor.par()) {
2268 ownerParagraph(ownerParagraph()->next());
2271 delete old_cursor.par();
2273 /* Breakagain the next par. Needed because of
2274 the parindent that can occur or dissappear.
2275 The next row can change its height, if
2276 there is another layout before */
2278 breakAgain(bview, refresh_row);
2279 updateCounters(bview);
2284 setCursorIntern(bview, cursor.par(), cursor.pos());
2286 if (selection.cursor.par() == old_cursor.par()
2287 && selection.cursor.pos() == old_cursor.pos()) {
2288 // correct selection
2289 selection.cursor = cursor;
2293 if (old_cursor.par()->stripLeadingSpaces()) {
2294 redoParagraphs(bview, old_cursor,
2295 old_cursor.par()->next());
2297 setCursorIntern(bview, cursor.par(), cursor.pos());
2298 selection.cursor = cursor;
2305 Paragraph * LyXText::ownerParagraph() const
2308 return inset_owner->paragraph();
2310 return &*(bv_owner->buffer()->paragraphs.begin());
2314 void LyXText::ownerParagraph(Paragraph * p) const
2317 inset_owner->paragraph(p);
2319 bv_owner->buffer()->paragraphs.set(p);
2324 void LyXText::ownerParagraph(int id, Paragraph * p) const
2326 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2327 if (op && op->inInset()) {
2328 static_cast<InsetText *>(op->inInset())->paragraph(p);
2335 LyXText::text_status LyXText::status() const
2341 void LyXText::status(BufferView * bview, LyXText::text_status st) const
2343 LyXText * t = bview->text;
2345 // We should only go up with refreshing code so this means that if
2346 // we have a MORE refresh we should never set it to LITTLE if we still
2347 // didn't handle it (and then it will be UNCHANGED. Now as long as
2348 // we stay inside one LyXText this may work but we need to tell the
2349 // outermost LyXText that it should REALLY draw us if there is some
2350 // change in a Inset::LyXText. So you see that when we are inside a
2351 // inset's LyXText we give the LITTLE to the outermost LyXText to
2352 // tell'em that it should redraw the actual row (where the inset
2353 // resides! Capito?!
2355 if (status_ != NEED_MORE_REFRESH || st != NEED_VERY_LITTLE_REFRESH) {
2357 if (inset_owner && st != UNCHANGED) {
2358 t->status(bview, NEED_VERY_LITTLE_REFRESH);
2359 if (!t->refresh_row) {
2360 t->refresh_row = t->cursor.row();
2361 t->refresh_y = t->cursor.y() -
2362 t->cursor.row()->baseline();
2369 bool LyXText::isTopLevel() const
2371 /// only the top-level lyxtext has a non-null bv owner
2376 int defaultRowHeight()
2378 LyXFont const font(LyXFont::ALL_SANE);
2379 return int(font_metrics::maxAscent(font)
2380 + font_metrics::maxDescent(font) * 1.5);