1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
33 #include "insets/inseterror.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetspecialchar.h"
36 #include "insets/insettext.h"
37 #include "insets/insetfloat.h"
38 #include "insets/insetwrap.h"
40 #include "support/LAssert.h"
41 #include "support/textutils.h"
42 #include "support/lstrings.h"
44 #include "BoostFormat.h"
54 LyXText::LyXText(BufferView * bv)
55 : height(0), width(0), anchor_row_(0), anchor_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), need_break_row(0),
57 bv_owner(bv), firstrow(0), lastrow(0)
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), anchor_row_(0), anchor_row_offset_(0),
65 inset_owner(inset), the_locking_inset(0), need_break_row(0),
66 bv_owner(bv), firstrow(0), lastrow(0)
72 void LyXText::init(BufferView * bview, bool reinit)
75 // Delete all rows, this does not touch the paragraphs!
76 Row * tmprow = firstrow;
78 tmprow = firstrow->next();
86 copylayouttype.erase();
92 Paragraph * par = ownerParagraph();
93 current_font = getFont(bview->buffer(), par, 0);
96 insertParagraph(par, lastrow);
99 setCursorIntern(firstrow->par(), 0);
100 selection.cursor = cursor;
108 // Delete all rows, this does not touch the paragraphs!
109 Row * tmprow = firstrow;
111 tmprow = firstrow->next();
120 LyXFont const realizeFont(LyXFont const & font,
124 LyXTextClass const & tclass = buf->params.getLyXTextClass();
125 LyXFont tmpfont(font);
126 Paragraph::depth_type par_depth = par->getDepth();
128 // Resolve against environment font information
129 while (par && par_depth && !tmpfont.resolved()) {
130 par = par->outerHook();
132 tmpfont.realize(par->layout()->font);
133 par_depth = par->getDepth();
137 tmpfont.realize(tclass.defaultfont());
145 // Gets the fully instantiated font at a given position in a paragraph
146 // Basically the same routine as Paragraph::getFont() in paragraph.C.
147 // The difference is that this one is used for displaying, and thus we
148 // are allowed to make cosmetic improvements. For instance make footnotes
150 // If position is -1, we get the layout font of the paragraph.
151 // If position is -2, we get the font of the manual label of the paragraph.
152 LyXFont const LyXText::getFont(Buffer const * buf, Paragraph * par,
155 lyx::Assert(pos >= 0);
157 LyXLayout_ptr const & layout = par->layout();
159 // We specialize the 95% common case:
160 if (!par->getDepth()) {
161 if (layout->labeltype == LABEL_MANUAL
162 && pos < par->beginningOfBody()) {
164 LyXFont f = par->getFontSettings(buf->params, pos);
166 par->inInset()->getDrawFont(f);
167 return f.realize(layout->reslabelfont);
169 LyXFont f = par->getFontSettings(buf->params, pos);
171 par->inInset()->getDrawFont(f);
172 return f.realize(layout->resfont);
176 // The uncommon case need not be optimized as much
180 if (pos < par->beginningOfBody()) {
182 layoutfont = layout->labelfont;
185 layoutfont = layout->font;
188 LyXFont tmpfont = par->getFontSettings(buf->params, pos);
189 tmpfont.realize(layoutfont);
192 par->inInset()->getDrawFont(tmpfont);
194 return realizeFont(tmpfont, buf, par);
198 LyXFont const LyXText::getLayoutFont(Buffer const * buf, Paragraph * par) const
200 LyXLayout_ptr const & layout = par->layout();
202 if (!par->getDepth()) {
203 return layout->resfont;
206 return realizeFont(layout->font, buf, par);
210 LyXFont const LyXText::getLabelFont(Buffer const * buf, Paragraph * par) const
212 LyXLayout_ptr const & layout = par->layout();
214 if (!par->getDepth()) {
215 return layout->reslabelfont;
218 return realizeFont(layout->labelfont, buf, par);
222 void LyXText::setCharFont(Paragraph * par,
223 pos_type pos, LyXFont const & fnt,
226 Buffer const * buf = bv()->buffer();
227 LyXFont font = getFont(buf, par, pos);
228 font.update(fnt, buf->params.language, toggleall);
229 // Let the insets convert their font
230 if (par->isInset(pos)) {
231 Inset * inset = par->getInset(pos);
232 if (isEditableInset(inset)) {
233 UpdatableInset * uinset =
234 static_cast<UpdatableInset *>(inset);
235 uinset->setFont(bv(), fnt, toggleall, true);
239 // Plug thru to version below:
240 setCharFont(buf, par, pos, font);
244 void LyXText::setCharFont(Buffer const * buf, Paragraph * par,
245 pos_type pos, LyXFont const & fnt)
249 LyXTextClass const & tclass = buf->params.getLyXTextClass();
250 LyXLayout_ptr const & layout = par->layout();
252 // Get concrete layout font to reduce against
255 if (pos < par->beginningOfBody())
256 layoutfont = layout->labelfont;
258 layoutfont = layout->font;
260 // Realize against environment font information
261 if (par->getDepth()) {
262 Paragraph * tp = par;
263 while (!layoutfont.resolved() && tp && tp->getDepth()) {
264 tp = tp->outerHook();
266 layoutfont.realize(tp->layout()->font);
270 layoutfont.realize(tclass.defaultfont());
272 // Now, reduce font against full layout font
273 font.reduce(layoutfont);
275 par->setFont(pos, font);
279 // inserts a new row before the specified row, increments
280 // the touched counters
281 void LyXText::insertRow(Row * row, Paragraph * par,
284 Row * tmprow = new Row;
287 tmprow->next(firstrow);
290 tmprow->previous(row);
291 tmprow->next(row->next());
296 tmprow->next()->previous(tmprow);
298 if (tmprow->previous())
299 tmprow->previous()->next(tmprow);
310 // removes the row and reset the touched counters
311 void LyXText::removeRow(Row * row)
313 Row * row_prev = row->previous();
315 row->next()->previous(row_prev);
317 firstrow = row->next();
318 // lyx::Assert(firstrow);
320 row_prev->next(row->next());
322 if (row == lastrow) {
323 lyx::Assert(!row->next());
327 /* FIXME: when we cache the bview, this should just
328 * become a postPaint(), I think */
329 if (refresh_row == row) {
330 refresh_row = row_prev ? row_prev : row->next();
331 // what about refresh_y
334 if (anchor_row_ == row) {
336 anchor_row_ = row_prev;
337 anchor_row_offset_ = 0;
339 anchor_row_ = row->next();
340 anchor_row_offset_ -= row->height();
344 height -= row->height(); // the text becomes smaller
350 // remove all following rows of the paragraph of the specified row.
351 void LyXText::removeParagraph(Row * row)
353 Paragraph * tmppar = row->par();
357 while (row && row->par() == tmppar) {
358 tmprow = row->next();
365 // insert the specified paragraph behind the specified row
366 void LyXText::insertParagraph(Paragraph * par,
369 // insert a new row, starting at position 0
370 insertRow(row, par, 0);
372 // and now append the whole paragraph before the new row
375 appendParagraph(firstrow);
377 row->next()->height(0);
378 appendParagraph(row->next());
383 Inset * LyXText::getInset() const
385 if (cursor.pos() < cursor.par()->size()
386 && cursor.par()->isInset(cursor.pos())) {
387 return cursor.par()->getInset(cursor.pos());
393 void LyXText::toggleInset()
395 Inset * inset = getInset();
396 // is there an editable inset at cursor position?
397 if (!isEditableInset(inset)) {
398 // No, try to see if we are inside a collapsable inset
399 if (inset_owner && inset_owner->owner()
400 && inset_owner->owner()->isOpen()) {
401 bv()->unlockInset(static_cast<UpdatableInset *>(inset_owner->owner()));
402 inset_owner->owner()->close(bv());
403 bv()->getLyXText()->cursorRight(bv());
407 //bv()->owner()->message(inset->editMessage());
409 // do we want to keep this?? (JMarc)
410 if (!isHighlyEditableInset(inset))
411 setCursorParUndo(bv());
413 if (inset->isOpen()) {
419 inset->open(bv(), !inset->isOpen());
424 /* used in setlayout */
425 // Asger is not sure we want to do this...
426 void LyXText::makeFontEntriesLayoutSpecific(Buffer const & buf,
429 LyXLayout_ptr const & layout = par.layout();
432 for (pos_type pos = 0; pos < par.size(); ++pos) {
433 if (pos < par.beginningOfBody())
434 layoutfont = layout->labelfont;
436 layoutfont = layout->font;
438 LyXFont tmpfont = par.getFontSettings(buf.params, pos);
439 tmpfont.reduce(layoutfont);
440 par.setFont(pos, tmpfont);
445 Paragraph * LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
446 LyXCursor & send_cur,
447 string const & layout)
449 Paragraph * endpar = send_cur.par()->next();
450 Paragraph * undoendpar = endpar;
452 if (endpar && endpar->getDepth()) {
453 while (endpar && endpar->getDepth()) {
454 endpar = endpar->next();
458 endpar = endpar->next(); // because of parindents etc.
461 setUndo(bv(), Undo::EDIT, sstart_cur.par(), undoendpar);
463 // ok we have a selection. This is always between sstart_cur
464 // and sel_end cursor
466 Paragraph * par = sstart_cur.par();
467 Paragraph * epar = send_cur.par()->next();
469 LyXLayout_ptr const & lyxlayout =
470 bv()->buffer()->params.getLyXTextClass()[layout];
473 par->applyLayout(lyxlayout);
474 makeFontEntriesLayoutSpecific(*bv()->buffer(), *par);
475 Paragraph * fppar = par;
476 fppar->params().spaceTop(lyxlayout->fill_top ?
477 VSpace(VSpace::VFILL)
478 : VSpace(VSpace::NONE));
479 fppar->params().spaceBottom(lyxlayout->fill_bottom ?
480 VSpace(VSpace::VFILL)
481 : VSpace(VSpace::NONE));
482 if (lyxlayout->margintype == MARGIN_MANUAL)
483 par->setLabelWidthString(lyxlayout->labelstring());
486 } while (par != epar);
492 // set layout over selection and make a total rebreak of those paragraphs
493 void LyXText::setLayout(string const & layout)
495 LyXCursor tmpcursor = cursor; /* store the current cursor */
497 // if there is no selection just set the layout
498 // of the current paragraph */
499 if (!selection.set()) {
500 selection.start = cursor; // dummy selection
501 selection.end = cursor;
503 Paragraph * endpar = setLayout(cursor, selection.start,
504 selection.end, layout);
505 redoParagraphs(selection.start, endpar);
507 // we have to reset the selection, because the
508 // geometry could have changed
509 setCursor(selection.start.par(),
510 selection.start.pos(), false);
511 selection.cursor = cursor;
512 setCursor(selection.end.par(), selection.end.pos(), false);
516 setCursor(tmpcursor.par(), tmpcursor.pos(), true);
520 // increment depth over selection and
521 // make a total rebreak of those paragraphs
522 void LyXText::incDepth()
524 // If there is no selection, just use the current paragraph
525 if (!selection.set()) {
526 selection.start = cursor; // dummy selection
527 selection.end = cursor;
530 // We end at the next paragraph with depth 0
531 Paragraph * endpar = selection.end.par()->next();
533 Paragraph * undoendpar = endpar;
535 if (endpar && endpar->getDepth()) {
536 while (endpar && endpar->getDepth()) {
537 endpar = endpar->next();
541 endpar = endpar->next(); // because of parindents etc.
544 setUndo(bv(), Undo::EDIT,
545 selection.start.par(), undoendpar);
547 LyXCursor tmpcursor = cursor; // store the current cursor
549 // ok we have a selection. This is always between sel_start_cursor
550 // and sel_end cursor
551 cursor = selection.start;
554 // NOTE: you can't change the depth of a bibliography entry
555 if (cursor.par()->layout()->labeltype != LABEL_BIBLIO) {
556 Paragraph * prev = cursor.par()->previous();
559 if (cursor.par()->getDepth()
560 < prev->getMaxDepthAfter()) {
561 cursor.par()->params().depth(cursor.par()->getDepth() + 1);
565 if (cursor.par() == selection.end.par())
567 cursor.par(cursor.par()->next());
570 redoParagraphs(selection.start, endpar);
572 // we have to reset the selection, because the
573 // geometry could have changed
574 setCursor(selection.start.par(), selection.start.pos());
575 selection.cursor = cursor;
576 setCursor(selection.end.par(), selection.end.pos());
580 setCursor(tmpcursor.par(), tmpcursor.pos());
584 // decrement depth over selection and
585 // make a total rebreak of those paragraphs
586 void LyXText::decDepth()
588 // if there is no selection just set the layout
589 // of the current paragraph
590 if (!selection.set()) {
591 selection.start = cursor; // dummy selection
592 selection.end = cursor;
594 Paragraph * endpar = selection.end.par()->next();
595 Paragraph * undoendpar = endpar;
597 if (endpar && endpar->getDepth()) {
598 while (endpar && endpar->getDepth()) {
599 endpar = endpar->next();
603 endpar = endpar->next(); // because of parindents etc.
606 setUndo(bv(), Undo::EDIT,
607 selection.start.par(), undoendpar);
609 LyXCursor tmpcursor = cursor; // store the current cursor
611 // ok we have a selection. This is always between sel_start_cursor
612 // and sel_end cursor
613 cursor = selection.start;
616 if (cursor.par()->params().depth()) {
617 cursor.par()->params()
618 .depth(cursor.par()->params().depth() - 1);
620 if (cursor.par() == selection.end.par()) {
623 cursor.par(cursor.par()->next());
626 redoParagraphs(selection.start, endpar);
628 // we have to reset the selection, because the
629 // geometry could have changed
630 setCursor(selection.start.par(),
631 selection.start.pos());
632 selection.cursor = cursor;
633 setCursor(selection.end.par(), selection.end.pos());
637 setCursor(tmpcursor.par(), tmpcursor.pos());
641 // set font over selection and make a total rebreak of those paragraphs
642 void LyXText::setFont(LyXFont const & font, bool toggleall)
644 // if there is no selection just set the current_font
645 if (!selection.set()) {
646 // Determine basis font
648 if (cursor.pos() < cursor.par()->beginningOfBody()) {
649 layoutfont = getLabelFont(bv()->buffer(),
652 layoutfont = getLayoutFont(bv()->buffer(),
655 // Update current font
656 real_current_font.update(font,
657 bv()->buffer()->params.language,
660 // Reduce to implicit settings
661 current_font = real_current_font;
662 current_font.reduce(layoutfont);
663 // And resolve it completely
664 real_current_font.realize(layoutfont);
669 LyXCursor tmpcursor = cursor; // store the current cursor
671 // ok we have a selection. This is always between sel_start_cursor
672 // and sel_end cursor
674 setUndo(bv(), Undo::EDIT,
675 selection.start.par(), selection.end.par()->next());
677 cursor = selection.start;
678 while (cursor.par() != selection.end.par() ||
679 cursor.pos() < selection.end.pos())
681 if (cursor.pos() < cursor.par()->size()) {
682 // an open footnote should behave like a closed one
683 setCharFont(cursor.par(), cursor.pos(),
685 cursor.pos(cursor.pos() + 1);
688 cursor.par(cursor.par()->next());
693 redoParagraphs(selection.start, selection.end.par()->next());
695 // we have to reset the selection, because the
696 // geometry could have changed, but we keep
697 // it for user convenience
698 setCursor(selection.start.par(), selection.start.pos());
699 selection.cursor = cursor;
700 setCursor(selection.end.par(), selection.end.pos());
702 setCursor(tmpcursor.par(), tmpcursor.pos(), true,
703 tmpcursor.boundary());
707 void LyXText::redoHeightOfParagraph()
709 Row * tmprow = cursor.row();
710 int y = cursor.y() - tmprow->baseline();
712 setHeightOfRow(tmprow);
714 while (tmprow->previous()
715 && tmprow->previous()->par() == tmprow->par()) {
716 tmprow = tmprow->previous();
717 y -= tmprow->height();
718 setHeightOfRow(tmprow);
723 setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
727 void LyXText::redoDrawingOfParagraph(LyXCursor const & cur)
729 Row * tmprow = cur.row();
731 int y = cur.y() - tmprow->baseline();
732 setHeightOfRow(tmprow);
734 while (tmprow->previous()
735 && tmprow->previous()->par() == tmprow->par()) {
736 tmprow = tmprow->previous();
737 y -= tmprow->height();
741 setCursor(cur.par(), cur.pos());
745 // deletes and inserts again all paragaphs between the cursor
746 // and the specified par
747 // This function is needed after SetLayout and SetFont etc.
748 void LyXText::redoParagraphs(LyXCursor const & cur,
749 Paragraph const * endpar)
752 Paragraph * tmppar = 0;
753 Paragraph * first_phys_par = 0;
755 Row * tmprow = cur.row();
757 int y = cur.y() - tmprow->baseline();
759 if (!tmprow->previous()) {
760 // a trick/hack for UNDO
761 // This is needed because in an UNDO/REDO we could have changed
762 // the ownerParagrah() so the paragraph inside the row is NOT
763 // my really first par anymore. Got it Lars ;) (Jug 20011206)
764 first_phys_par = ownerParagraph();
766 first_phys_par = tmprow->par();
767 while (tmprow->previous()
768 && tmprow->previous()->par() == first_phys_par)
770 tmprow = tmprow->previous();
771 y -= tmprow->height();
775 Row * prevrow = tmprow->previous();
779 tmppar = tmprow->next()->par();
782 while (tmprow->next() && tmppar != endpar) {
783 removeRow(tmprow->next());
784 if (tmprow->next()) {
785 tmppar = tmprow->next()->par();
791 // remove the first one
792 tmprow2 = tmprow; /* this is because tmprow->previous()
794 tmprow = tmprow->previous();
797 tmppar = first_phys_par;
801 insertParagraph(tmppar, tmprow);
805 while (tmprow->next()
806 && tmprow->next()->par() == tmppar) {
807 tmprow = tmprow->next();
809 tmppar = tmppar->next();
811 } while (tmppar && tmppar != endpar);
813 // this is because of layout changes
815 setHeightOfRow(prevrow);
816 const_cast<LyXText *>(this)->postPaint(y - prevrow->height());
818 setHeightOfRow(firstrow);
819 const_cast<LyXText *>(this)->postPaint(0);
822 if (tmprow && tmprow->next())
823 setHeightOfRow(tmprow->next());
828 void LyXText::fullRebreak()
834 if (need_break_row) {
835 breakAgain(need_break_row);
842 // important for the screen
845 // the cursor set functions have a special mechanism. When they
846 // realize, that you left an empty paragraph, they will delete it.
847 // They also delete the corresponding row
849 // need the selection cursor:
850 void LyXText::setSelection()
852 bool const lsel = selection.set();
854 if (!selection.set()) {
855 last_sel_cursor = selection.cursor;
856 selection.start = selection.cursor;
857 selection.end = selection.cursor;
862 // first the toggling area
863 if (cursor.y() < last_sel_cursor.y()
864 || (cursor.y() == last_sel_cursor.y()
865 && cursor.x() < last_sel_cursor.x())) {
866 toggle_end_cursor = last_sel_cursor;
867 toggle_cursor = cursor;
869 toggle_end_cursor = cursor;
870 toggle_cursor = last_sel_cursor;
873 last_sel_cursor = cursor;
875 // and now the whole selection
877 if (selection.cursor.par() == cursor.par())
878 if (selection.cursor.pos() < cursor.pos()) {
879 selection.end = cursor;
880 selection.start = selection.cursor;
882 selection.end = selection.cursor;
883 selection.start = cursor;
885 else if (selection.cursor.y() < cursor.y() ||
886 (selection.cursor.y() == cursor.y()
887 && selection.cursor.x() < cursor.x())) {
888 selection.end = cursor;
889 selection.start = selection.cursor;
892 selection.end = selection.cursor;
893 selection.start = cursor;
896 // a selection with no contents is not a selection
897 if (selection.start.par() == selection.end.par() &&
898 selection.start.pos() == selection.end.pos())
899 selection.set(false);
901 if (inset_owner && (selection.set() || lsel))
902 inset_owner->setUpdateStatus(bv(), InsetText::SELECTION);
906 string const LyXText::selectionAsString(Buffer const * buffer,
909 if (!selection.set()) return string();
911 // should be const ...
912 Paragraph * startpar(selection.start.par());
913 Paragraph * endpar(selection.end.par());
914 pos_type const startpos(selection.start.pos());
915 pos_type const endpos(selection.end.pos());
917 if (startpar == endpar) {
918 return startpar->asString(buffer, startpos, endpos, label);
923 // First paragraph in selection
924 result += startpar->asString(buffer, startpos, startpar->size(), label) + "\n\n";
926 // The paragraphs in between (if any)
927 LyXCursor tmpcur(selection.start);
928 tmpcur.par(tmpcur.par()->next());
929 while (tmpcur.par() != endpar) {
930 result += tmpcur.par()->asString(buffer, 0,
931 tmpcur.par()->size(),
933 tmpcur.par(tmpcur.par()->next());
936 // Last paragraph in selection
937 result += endpar->asString(buffer, 0, endpos, label);
943 void LyXText::clearSelection()
945 selection.set(false);
946 selection.mark(false);
947 last_sel_cursor = selection.end = selection.start = selection.cursor = cursor;
948 // reset this in the bv_owner!
949 if (bv_owner && bv_owner->text)
950 bv_owner->text->xsel_cache.set(false);
954 void LyXText::cursorHome()
956 setCursor(cursor.par(), cursor.row()->pos());
960 void LyXText::cursorEnd()
962 if (!cursor.row()->next()
963 || cursor.row()->next()->par() != cursor.row()->par()) {
964 setCursor(cursor.par(), cursor.row()->lastPos() + 1);
966 if (!cursor.par()->empty() &&
967 (cursor.par()->getChar(cursor.row()->lastPos()) == ' '
968 || cursor.par()->isNewline(cursor.row()->lastPos()))) {
969 setCursor(cursor.par(), cursor.row()->lastPos());
971 setCursor(cursor.par(),
972 cursor.row()->lastPos() + 1);
978 void LyXText::cursorTop()
980 while (cursor.par()->previous())
981 cursor.par(cursor.par()->previous());
982 setCursor(cursor.par(), 0);
986 void LyXText::cursorBottom()
988 while (cursor.par()->next())
989 cursor.par(cursor.par()->next());
990 setCursor(cursor.par(), cursor.par()->size());
994 void LyXText::toggleFree(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 bv()->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(WHOLE_WORD_STRICT) : false;
1012 setFont(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(cursor.par(), cursor.pos());
1020 selection.cursor = cursor;
1023 inset_owner->setUpdateStatus(bv(), InsetText::CURSOR_PAR);
1027 string LyXText::getStringToIndex()
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(PREVIOUS_WORD);
1036 if (!selection.set())
1037 bv()->owner()->message(_("Nothing to index!"));
1038 else if (selection.start.par() != selection.end.par())
1039 bv()->owner()->message(_("Cannot index more than one paragraph!"));
1041 idxstring = selectionAsString(bv()->buffer(), false);
1043 // Reset cursors to their original position.
1044 cursor = reset_cursor;
1045 setCursor(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(bool line_top, bool line_bottom,
1063 bool pagebreak_top, bool pagebreak_bottom,
1064 VSpace const & space_top,
1065 VSpace const & space_bottom,
1066 Spacing const & spacing,
1068 string const & labelwidthstring,
1071 LyXCursor tmpcursor = cursor;
1072 if (!selection.set()) {
1073 selection.start = cursor;
1074 selection.end = cursor;
1077 // make sure that the depth behind the selection are restored, too
1078 Paragraph * endpar = selection.end.par()->next();
1079 Paragraph * undoendpar = endpar;
1081 if (endpar && endpar->getDepth()) {
1082 while (endpar && endpar->getDepth()) {
1083 endpar = endpar->next();
1084 undoendpar = endpar;
1088 // because of parindents etc.
1089 endpar = endpar->next();
1092 setUndo(bv(), Undo::EDIT, selection.start.par(), undoendpar);
1095 Paragraph * tmppar = selection.end.par();
1097 while (tmppar != selection.start.par()->previous()) {
1098 setCursor(tmppar, 0);
1099 postPaint(cursor.y() - cursor.row()->baseline());
1100 cursor.par()->params().lineTop(line_top);
1101 cursor.par()->params().lineBottom(line_bottom);
1102 cursor.par()->params().pagebreakTop(pagebreak_top);
1103 cursor.par()->params().pagebreakBottom(pagebreak_bottom);
1104 cursor.par()->params().spaceTop(space_top);
1105 cursor.par()->params().spaceBottom(space_bottom);
1106 cursor.par()->params().spacing(spacing);
1107 // does the layout allow the new alignment?
1108 LyXLayout_ptr const & layout = cursor.par()->layout();
1110 if (align == LYX_ALIGN_LAYOUT)
1111 align = layout->align;
1112 if (align & layout->alignpossible) {
1113 if (align == layout->align)
1114 cursor.par()->params().align(LYX_ALIGN_LAYOUT);
1116 cursor.par()->params().align(align);
1118 cursor.par()->setLabelWidthString(labelwidthstring);
1119 cursor.par()->params().noindent(noindent);
1120 tmppar = cursor.par()->previous();
1123 redoParagraphs(selection.start, endpar);
1126 setCursor(selection.start.par(), selection.start.pos());
1127 selection.cursor = cursor;
1128 setCursor(selection.end.par(), selection.end.pos());
1130 setCursor(tmpcursor.par(), tmpcursor.pos());
1132 bv()->updateInset(inset_owner);
1136 // set the counter of a paragraph. This includes the labels
1137 void LyXText::setCounter(Buffer const * buf, Paragraph * par)
1139 LyXTextClass const & textclass = buf->params.getLyXTextClass();
1140 LyXLayout_ptr const & layout = par->layout();
1142 if (par->previous()) {
1144 par->params().appendix(par->previous()->params().appendix());
1145 if (!par->params().appendix() && par->params().startOfAppendix()) {
1146 par->params().appendix(true);
1147 textclass.counters().reset();
1149 par->enumdepth = par->previous()->enumdepth;
1150 par->itemdepth = par->previous()->itemdepth;
1152 par->params().appendix(par->params().startOfAppendix());
1157 /* Maybe we have to increment the enumeration depth.
1158 * BUT, enumeration in a footnote is considered in isolation from its
1159 * surrounding paragraph so don't increment if this is the
1160 * first line of the footnote
1161 * AND, bibliographies can't have their depth changed ie. they
1162 * are always of depth 0
1165 && par->previous()->getDepth() < par->getDepth()
1166 && par->previous()->layout()->labeltype == LABEL_COUNTER_ENUMI
1167 && par->enumdepth < 3
1168 && layout->labeltype != LABEL_BIBLIO) {
1172 // Maybe we have to decrement the enumeration depth, see note above
1174 && par->previous()->getDepth() > par->getDepth()
1175 && layout->labeltype != LABEL_BIBLIO) {
1176 par->enumdepth = par->depthHook(par->getDepth())->enumdepth;
1179 if (!par->params().labelString().empty()) {
1180 par->params().labelString(string());
1183 if (layout->margintype == MARGIN_MANUAL) {
1184 if (par->params().labelWidthString().empty()) {
1185 par->setLabelWidthString(layout->labelstring());
1188 par->setLabelWidthString(string());
1191 // is it a layout that has an automatic label?
1192 if (layout->labeltype >= LABEL_COUNTER_CHAPTER) {
1193 int const i = layout->labeltype - LABEL_COUNTER_CHAPTER;
1197 if (i >= 0 && i <= buf->params.secnumdepth) {
1201 textclass.counters().step(layout->latexname());
1203 // Is there a label? Useful for Chapter layout
1204 if (!par->params().appendix()) {
1205 s << layout->labelstring();
1207 s << layout->labelstring_appendix();
1210 // Use of an integer is here less than elegant. For now.
1211 int head = textclass.maxcounter() - LABEL_COUNTER_CHAPTER;
1212 if (!par->params().appendix()) {
1213 numbertype = "sectioning";
1215 numbertype = "appendix";
1216 if (par->isRightToLeftPar(buf->params))
1217 langtype = "hebrew";
1222 s << textclass.counters()
1223 .numberLabel(layout->latexname(),
1224 numbertype, langtype, head);
1226 par->params().labelString(STRCONV(s.str()));
1228 // reset enum counters
1229 textclass.counters().reset("enum");
1230 } else if (layout->labeltype < LABEL_COUNTER_ENUMI) {
1231 textclass.counters().reset("enum");
1232 } else if (layout->labeltype == LABEL_COUNTER_ENUMI) {
1234 // Yes I know this is a really, really! bad solution
1236 string enumcounter("enum");
1238 switch (par->enumdepth) {
1247 enumcounter += "iv";
1250 // not a valid enumdepth...
1254 textclass.counters().step(enumcounter);
1256 s << textclass.counters()
1257 .numberLabel(enumcounter, "enumeration");
1258 par->params().labelString(STRCONV(s.str()));
1260 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1261 textclass.counters().step("bibitem");
1262 int number = textclass.counters().value("bibitem");
1263 if (par->bibitem()) {
1264 par->bibitem()->setCounter(number);
1265 par->params().labelString(layout->labelstring());
1267 // In biblio should't be following counters but...
1269 string s = layout->labelstring();
1271 // the caption hack:
1272 if (layout->labeltype == LABEL_SENSITIVE) {
1273 Paragraph * tmppar = par;
1276 while (tmppar && tmppar->inInset()
1277 // the single '=' is intended below
1278 && (in = tmppar->inInset()->owner())) {
1279 if (in->lyxCode() == Inset::FLOAT_CODE ||
1280 in->lyxCode() == Inset::WRAP_CODE) {
1284 tmppar = in->parOwner();
1290 = textclass.floats().getType(static_cast<InsetFloat*>(in)->type());
1292 textclass.counters().step(fl.type());
1294 // Doesn't work... yet.
1295 #warning use boost.format
1296 #if USE_BOOST_FORMAT
1297 s = boost::io::str(boost::format(_("%1$s #:")) % fl.name());
1298 // s << boost::format(_("%1$s %1$d:")
1300 // % buf->counters().value(fl.name());
1303 //o << fl.name() << ' ' << buf->counters().value(fl.name()) << ":";
1304 o << fl.name() << " #:";
1305 s = STRCONV(o.str());
1308 // par->SetLayout(0);
1309 // s = layout->labelstring;
1310 s = _("Senseless: ");
1313 par->params().labelString(s);
1315 // reset the enumeration counter. They are always reset
1316 // when there is any other layout between
1317 // Just fall-through between the cases so that all
1318 // enum counters deeper than enumdepth is also reset.
1319 switch (par->enumdepth) {
1321 textclass.counters().reset("enumi");
1323 textclass.counters().reset("enumii");
1325 textclass.counters().reset("enumiii");
1327 textclass.counters().reset("enumiv");
1333 // Updates all counters. Paragraphs with changed label string will be rebroken
1334 void LyXText::updateCounters()
1336 Row * row = firstrow;
1337 Paragraph * par = row->par();
1339 // CHECK if this is really needed. (Lgb)
1340 bv()->buffer()->params.getLyXTextClass().counters().reset();
1343 while (row->par() != par)
1346 string const oldLabel = par->params().labelString();
1348 // setCounter can potentially change the labelString.
1349 setCounter(bv()->buffer(), par);
1351 string const & newLabel = par->params().labelString();
1353 if (oldLabel.empty() && !newLabel.empty()) {
1354 removeParagraph(row);
1355 appendParagraph(row);
1363 void LyXText::insertInset(Inset * inset)
1365 if (!cursor.par()->insetAllowed(inset->lyxCode()))
1367 setUndo(bv(), Undo::FINISH, cursor.par(), cursor.par()->next());
1369 cursor.par()->insertInset(cursor.pos(), inset);
1370 // Just to rebreak and refresh correctly.
1371 // The character will not be inserted a second time
1372 insertChar(Paragraph::META_INSET);
1373 // If we enter a highly editable inset the cursor should be to before
1374 // the inset. This couldn't happen before as Undo was not handled inside
1375 // inset now after the Undo LyX tries to call inset->Edit(...) again
1376 // and cannot do this as the cursor is behind the inset and GetInset
1377 // does not return the inset!
1378 if (isHighlyEditableInset(inset)) {
1385 void LyXText::copyEnvironmentType()
1387 copylayouttype = cursor.par()->layout()->name();
1391 void LyXText::pasteEnvironmentType()
1393 // do nothing if there has been no previous copyEnvironmentType()
1394 if (!copylayouttype.empty())
1395 setLayout(copylayouttype);
1399 void LyXText::cutSelection(bool doclear, bool realcut)
1401 // Stuff what we got on the clipboard. Even if there is no selection.
1403 // There is a problem with having the stuffing here in that the
1404 // larger the selection the slower LyX will get. This can be
1405 // solved by running the line below only when the selection has
1406 // finished. The solution used currently just works, to make it
1407 // faster we need to be more clever and probably also have more
1408 // calls to stuffClipboard. (Lgb)
1409 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1411 // This doesn't make sense, if there is no selection
1412 if (!selection.set())
1415 // OK, we have a selection. This is always between selection.start
1416 // and selection.end
1418 // make sure that the depth behind the selection are restored, too
1419 Paragraph * endpar = selection.end.par()->next();
1420 Paragraph * undoendpar = endpar;
1422 if (endpar && endpar->getDepth()) {
1423 while (endpar && endpar->getDepth()) {
1424 endpar = endpar->next();
1425 undoendpar = endpar;
1427 } else if (endpar) {
1428 endpar = endpar->next(); // because of parindents etc.
1431 setUndo(bv(), Undo::DELETE,
1432 selection.start.par(), undoendpar);
1434 // there are two cases: cut only within one paragraph or
1435 // more than one paragraph
1436 if (selection.start.par() == selection.end.par()) {
1437 // only within one paragraph
1438 endpar = selection.end.par();
1439 int pos = selection.end.pos();
1440 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1441 selection.start.pos(), pos,
1442 bv()->buffer()->params.textclass,
1444 selection.end.pos(pos);
1446 endpar = selection.end.par();
1447 int pos = selection.end.pos();
1448 CutAndPaste::cutSelection(selection.start.par(), &endpar,
1449 selection.start.pos(), pos,
1450 bv()->buffer()->params.textclass,
1453 selection.end.par(endpar);
1454 selection.end.pos(pos);
1455 cursor.pos(selection.end.pos());
1457 endpar = endpar->next();
1459 // sometimes necessary
1461 selection.start.par()->stripLeadingSpaces();
1463 redoParagraphs(selection.start, endpar);
1465 // cutSelection can invalidate the cursor so we need to set
1467 // we prefer the end for when tracking changes
1468 cursor = selection.end;
1470 // need a valid cursor. (Lgb)
1473 setCursor(cursor.par(), cursor.pos());
1474 selection.cursor = cursor;
1479 void LyXText::copySelection()
1481 // stuff the selection onto the X clipboard, from an explicit copy request
1482 bv()->stuffClipboard(selectionAsString(bv()->buffer(), true));
1484 // this doesnt make sense, if there is no selection
1485 if (!selection.set())
1488 // ok we have a selection. This is always between selection.start
1489 // and sel_end cursor
1491 // copy behind a space if there is one
1492 while (selection.start.par()->size() > selection.start.pos()
1493 && selection.start.par()->isLineSeparator(selection.start.pos())
1494 && (selection.start.par() != selection.end.par()
1495 || selection.start.pos() < selection.end.pos()))
1496 selection.start.pos(selection.start.pos() + 1);
1498 CutAndPaste::copySelection(selection.start.par(), selection.end.par(),
1499 selection.start.pos(), selection.end.pos(),
1500 bv()->buffer()->params.textclass);
1504 void LyXText::pasteSelection()
1506 // this does not make sense, if there is nothing to paste
1507 if (!CutAndPaste::checkPastePossible())
1510 setUndo(bv(), Undo::INSERT,
1511 cursor.par(), cursor.par()->next());
1514 Paragraph * actpar = cursor.par();
1515 int pos = cursor.pos();
1517 CutAndPaste::pasteSelection(&actpar, &endpar, pos,
1518 bv()->buffer()->params.textclass);
1520 redoParagraphs(cursor, endpar);
1522 setCursor(cursor.par(), cursor.pos());
1525 selection.cursor = cursor;
1526 setCursor(actpar, pos);
1532 void LyXText::setSelectionRange(lyx::pos_type length)
1537 selection.cursor = cursor;
1544 // simple replacing. The font of the first selected character is used
1545 void LyXText::replaceSelectionWithString(string const & str)
1547 setCursorParUndo(bv());
1550 if (!selection.set()) { // create a dummy selection
1551 selection.end = cursor;
1552 selection.start = cursor;
1555 // Get font setting before we cut
1556 pos_type pos = selection.end.pos();
1557 LyXFont const font = selection.start.par()
1558 ->getFontSettings(bv()->buffer()->params,
1559 selection.start.pos());
1561 // Insert the new string
1562 for (string::const_iterator cit = str.begin(); cit != str.end(); ++cit) {
1563 selection.end.par()->insertChar(pos, (*cit), font);
1567 // Cut the selection
1568 cutSelection(true, false);
1574 // needed to insert the selection
1575 void LyXText::insertStringAsLines(string const & str)
1577 Paragraph * par = cursor.par();
1578 pos_type pos = cursor.pos();
1579 Paragraph * endpar = cursor.par()->next();
1581 setCursorParUndo(bv());
1583 // only to be sure, should not be neccessary
1586 bv()->buffer()->insertStringAsLines(par, pos, current_font, str);
1588 redoParagraphs(cursor, endpar);
1589 setCursor(cursor.par(), cursor.pos());
1590 selection.cursor = cursor;
1591 setCursor(par, pos);
1596 // turns double-CR to single CR, others where converted into one
1597 // blank. Then InsertStringAsLines is called
1598 void LyXText::insertStringAsParagraphs(string const & str)
1600 string linestr(str);
1601 bool newline_inserted = false;
1602 for (string::size_type i = 0; i < linestr.length(); ++i) {
1603 if (linestr[i] == '\n') {
1604 if (newline_inserted) {
1605 // we know that \r will be ignored by
1606 // InsertStringA. Of course, it is a dirty
1607 // trick, but it works...
1608 linestr[i - 1] = '\r';
1612 newline_inserted = true;
1614 } else if (IsPrintable(linestr[i])) {
1615 newline_inserted = false;
1618 insertStringAsLines(linestr);
1622 void LyXText::checkParagraph(Paragraph * par,
1625 LyXCursor tmpcursor;
1629 Row * row = getRow(par, pos, y);
1631 // is there a break one row above
1632 if (row->previous() && row->previous()->par() == row->par()) {
1633 z = rowBreakPoint(*row->previous());
1634 if (z >= row->pos()) {
1635 // set the dimensions of the row above
1636 y -= row->previous()->height();
1639 breakAgain(row->previous());
1641 // set the cursor again. Otherwise
1642 // dangling pointers are possible
1643 setCursor(cursor.par(), cursor.pos(),
1644 false, cursor.boundary());
1645 selection.cursor = cursor;
1650 int const tmpheight = row->height();
1651 pos_type const tmplast = row->lastPos();
1654 if (row->height() == tmpheight && row->lastPos() == tmplast) {
1655 postRowPaint(row, y);
1660 // check the special right address boxes
1661 if (par->layout()->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
1668 redoDrawingOfParagraph(tmpcursor);
1671 // set the cursor again. Otherwise dangling pointers are possible
1672 // also set the selection
1674 if (selection.set()) {
1676 setCursorIntern(selection.cursor.par(), selection.cursor.pos(),
1677 false, selection.cursor.boundary());
1678 selection.cursor = cursor;
1679 setCursorIntern(selection.start.par(),
1680 selection.start.pos(),
1681 false, selection.start.boundary());
1682 selection.start = cursor;
1683 setCursorIntern(selection.end.par(),
1684 selection.end.pos(),
1685 false, selection.end.boundary());
1686 selection.end = cursor;
1687 setCursorIntern(last_sel_cursor.par(),
1688 last_sel_cursor.pos(),
1689 false, last_sel_cursor.boundary());
1690 last_sel_cursor = cursor;
1693 setCursorIntern(cursor.par(), cursor.pos(),
1694 false, cursor.boundary());
1698 // returns false if inset wasn't found
1699 bool LyXText::updateInset(Inset * inset)
1701 // first check the current paragraph
1702 int pos = cursor.par()->getPositionOfInset(inset);
1704 checkParagraph(cursor.par(), pos);
1708 // check every paragraph
1710 Paragraph * par = ownerParagraph();
1712 pos = par->getPositionOfInset(inset);
1714 checkParagraph(par, pos);
1724 bool LyXText::setCursor(Paragraph * par,
1726 bool setfont, bool boundary)
1728 LyXCursor old_cursor = cursor;
1729 setCursorIntern(par, pos, setfont, boundary);
1730 return deleteEmptyParagraphMechanism(old_cursor);
1734 void LyXText::setCursor(LyXCursor & cur, Paragraph * par,
1735 pos_type pos, bool boundary)
1741 cur.boundary(boundary);
1743 // get the cursor y position in text
1745 Row * row = getRow(par, pos, y);
1746 Row * old_row = row;
1748 // if we are before the first char of this row and are still in the
1749 // same paragraph and there is a previous row then put the cursor on
1750 // the end of the previous row
1751 cur.iy(y + row->baseline());
1753 if (row->previous() && pos &&
1754 row->previous()->par() == row->par() &&
1755 pos < par->size() &&
1756 par->getChar(pos) == Paragraph::META_INSET &&
1757 (ins = par->getInset(pos)) && (ins->needFullRow() || ins->display()))
1759 row = row->previous();
1764 // y is now the beginning of the cursor row
1765 y += row->baseline();
1766 // y is now the cursor baseline
1769 pos_type last = old_row->lastPrintablePos();
1771 // None of these should happen, but we're scaredy-cats
1772 if (pos > par->size()) {
1773 lyxerr << "dont like 1 please report" << endl;
1776 } else if (pos > last + 1) {
1777 lyxerr << "dont like 2 please report" << endl;
1778 // This shouldn't happen.
1781 } else if (pos < row->pos()) {
1782 lyxerr << "dont like 3 please report" << endl;
1787 // now get the cursors x position
1788 float x = getCursorX(row, pos, last, boundary);
1791 if (old_row != row) {
1792 x = getCursorX(old_row, pos, last, boundary);
1796 //if the cursor is in a visible row, anchor to it
1798 if (topy < y && y < topy + bv()->workHeight())
1803 float LyXText::getCursorX(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(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(bv()->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 body_pos = row->par()->beginningOfBody();
1831 if ((body_pos > 0) &&
1832 ((body_pos-1 > last) ||
1833 !row->par()->isLineSeparator(body_pos - 1)))
1836 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1837 pos_type pos = vis2log(vpos);
1838 if (body_pos > 0 && pos == body_pos - 1) {
1839 x += fill_label_hfill +
1840 font_metrics::width(
1841 row->par()->layout()->labelsep,
1842 getLabelFont(bv()->buffer(),
1844 if (row->par()->isLineSeparator(body_pos - 1))
1846 row->par(), body_pos - 1);
1848 if (row->hfillExpansion(pos)) {
1849 x += singleWidth(row->par(), pos);
1850 if (pos >= body_pos)
1853 x += fill_label_hfill;
1854 } else if (row->par()->isSeparator(pos)) {
1855 x += singleWidth(row->par(), pos);
1856 if (pos >= body_pos)
1857 x += fill_separator;
1859 x += singleWidth(row->par(), pos);
1865 void LyXText::setCursorIntern(Paragraph * par,
1866 pos_type pos, bool setfont, bool boundary)
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(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1894 setCursor(cursor, par, pos, boundary);
1900 void LyXText::setCurrentFont()
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(bv()->buffer()->params, pos);
1922 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1924 if (cursor.pos() == cursor.par()->size() &&
1925 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1926 !cursor.boundary()) {
1927 Language const * lang =
1928 cursor.par()->getParLanguage(bv()->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 // returns the column near the specified x-coordinate of the row
1938 // x is set to the real beginning of this column
1940 LyXText::getColumnNearX(Row * row, int & x,
1941 bool & boundary) const
1944 float fill_separator;
1946 float fill_label_hfill;
1948 prepareToPrint(row, tmpx, fill_separator,
1949 fill_hfill, fill_label_hfill);
1951 pos_type vc = row->pos();
1952 pos_type last = row->lastPrintablePos();
1955 LyXLayout_ptr const & layout = row->par()->layout();
1957 bool left_side = false;
1959 pos_type body_pos = row->par()->beginningOfBody();
1960 float last_tmpx = tmpx;
1963 (body_pos - 1 > last ||
1964 !row->par()->isLineSeparator(body_pos - 1)))
1967 // check for empty row
1968 if (!row->par()->size()) {
1973 while (vc <= last && tmpx <= x) {
1976 if (body_pos > 0 && c == body_pos-1) {
1977 tmpx += fill_label_hfill +
1978 font_metrics::width(layout->labelsep,
1979 getLabelFont(bv()->buffer(), row->par()));
1980 if (row->par()->isLineSeparator(body_pos - 1))
1981 tmpx -= singleWidth(row->par(), body_pos-1);
1984 if (row->hfillExpansion(c)) {
1985 tmpx += singleWidth(row->par(), c);
1989 tmpx += fill_label_hfill;
1990 } else if (row->par()->isSeparator(c)) {
1991 tmpx += singleWidth(row->par(), c);
1993 tmpx+= fill_separator;
1995 tmpx += singleWidth(row->par(), c);
2000 if ((tmpx + last_tmpx) / 2 > x) {
2005 if (vc > last + 1) // This shouldn't happen.
2009 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
2010 // some speedup if rtl_support=false
2011 && (!row->next() || row->next()->par() != row->par());
2012 bool const rtl = (lastrow)
2013 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
2014 : false; // If lastrow is false, we don't need to compute
2015 // the value of rtl.
2018 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
2019 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
2021 else if (vc == row->pos()) {
2023 if (bidi_level(c) % 2 == 1)
2026 c = vis2log(vc - 1);
2027 bool const rtl = (bidi_level(c) % 2 == 1);
2028 if (left_side == rtl) {
2030 boundary = isBoundary(bv()->buffer(), row->par(), c);
2034 if (row->pos() <= last && c > last
2035 && row->par()->isNewline(last)) {
2036 if (bidi_level(last) % 2 == 0)
2037 tmpx -= singleWidth(row->par(), last);
2039 tmpx += singleWidth(row->par(), last);
2049 void LyXText::setCursorFromCoordinates(int x, int y)
2051 LyXCursor old_cursor = cursor;
2053 setCursorFromCoordinates(cursor, x, y);
2055 deleteEmptyParagraphMechanism(old_cursor);
2062 * return true if the cursor given is at the end of a row,
2063 * and the next row is filled by an inset that spans an entire
2066 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2069 Row const & next = *row.next();
2071 if (next.pos() != cur.pos() || next.par() != cur.par())
2073 if (!cur.par()->isInset(cur.pos()))
2075 Inset const * inset = cur.par()->getInset(cur.pos());
2076 if (inset->needFullRow() || inset->display())
2083 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2086 // Get the row first.
2088 Row * row = getRowNearY(y);
2090 pos_type const column = getColumnNearX(row, x, bound);
2091 cur.par(row->par());
2092 cur.pos(row->pos() + column);
2094 cur.y(y + row->baseline());
2097 if (beforeFullRowInset(*row, cur)) {
2098 pos_type last = row->lastPrintablePos();
2099 float x = getCursorX(row->next(), cur.pos(), last, bound);
2101 cur.iy(y + row->height() + row->next()->baseline());
2102 cur.irow(row->next());
2108 cur.boundary(bound);
2112 void LyXText::cursorLeft(bool internal)
2114 if (cursor.pos() > 0) {
2115 bool boundary = cursor.boundary();
2116 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2117 if (!internal && !boundary &&
2118 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2119 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2120 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2121 Paragraph * par = cursor.par()->previous();
2122 setCursor(par, par->size());
2127 void LyXText::cursorRight(bool internal)
2129 if (!internal && cursor.boundary() &&
2130 !cursor.par()->isNewline(cursor.pos()))
2131 setCursor(cursor.par(), cursor.pos(), true, false);
2132 else if (cursor.pos() < cursor.par()->size()) {
2133 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2135 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2136 setCursor(cursor.par(), cursor.pos(), true, true);
2137 } else if (cursor.par()->next())
2138 setCursor(cursor.par()->next(), 0);
2142 void LyXText::cursorUp(bool selecting)
2145 int x = cursor.x_fix();
2146 int y = cursor.y() - cursor.row()->baseline() - 1;
2147 setCursorFromCoordinates(x, y);
2150 int y1 = cursor.iy() - topy;
2153 Inset * inset_hit = checkInsetHit(x, y1);
2154 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2155 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2159 setCursorFromCoordinates(bv(), cursor.x_fix(),
2160 cursor.y() - cursor.row()->baseline() - 1);
2165 void LyXText::cursorDown(bool selecting)
2168 int x = cursor.x_fix();
2169 int y = cursor.y() - cursor.row()->baseline() +
2170 cursor.row()->height() + 1;
2171 setCursorFromCoordinates(x, y);
2172 if (!selecting && cursor.row() == cursor.irow()) {
2174 int y1 = cursor.iy() - topy;
2177 Inset * inset_hit = checkInsetHit(x, y1);
2178 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2179 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2183 setCursorFromCoordinates(bv(), cursor.x_fix(),
2184 cursor.y() - cursor.row()->baseline()
2185 + cursor.row()->height() + 1);
2190 void LyXText::cursorUpParagraph()
2192 if (cursor.pos() > 0) {
2193 setCursor(cursor.par(), 0);
2195 else if (cursor.par()->previous()) {
2196 setCursor(cursor.par()->previous(), 0);
2201 void LyXText::cursorDownParagraph()
2203 if (cursor.par()->next()) {
2204 setCursor(cursor.par()->next(), 0);
2206 setCursor(cursor.par(), cursor.par()->size());
2210 // fix the cursor `cur' after a characters has been deleted at `where'
2211 // position. Called by deleteEmptyParagraphMechanism
2212 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2213 LyXCursor const & where)
2215 // if cursor is not in the paragraph where the delete occured,
2217 if (cur.par() != where.par())
2220 // if cursor position is after the place where the delete occured,
2222 if (cur.pos() > where.pos())
2223 cur.pos(cur.pos()-1);
2225 // check also if we don't want to set the cursor on a spot behind the
2226 // pagragraph because we erased the last character.
2227 if (cur.pos() > cur.par()->size())
2228 cur.pos(cur.par()->size());
2230 // recompute row et al. for this cursor
2231 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2235 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2237 // Would be wrong to delete anything if we have a selection.
2238 if (selection.set())
2241 // We allow all kinds of "mumbo-jumbo" when freespacing.
2242 if (old_cursor.par()->layout()->free_spacing
2243 || old_cursor.par()->isFreeSpacing()) {
2247 /* Ok I'll put some comments here about what is missing.
2248 I have fixed BackSpace (and thus Delete) to not delete
2249 double-spaces automagically. I have also changed Cut,
2250 Copy and Paste to hopefully do some sensible things.
2251 There are still some small problems that can lead to
2252 double spaces stored in the document file or space at
2253 the beginning of paragraphs. This happens if you have
2254 the cursor betwenn to spaces and then save. Or if you
2255 cut and paste and the selection have a space at the
2256 beginning and then save right after the paste. I am
2257 sure none of these are very hard to fix, but I will
2258 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2259 that I can get some feedback. (Lgb)
2262 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2263 // delete the LineSeparator.
2266 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2267 // delete the LineSeparator.
2270 // If the pos around the old_cursor were spaces, delete one of them.
2271 if (old_cursor.par() != cursor.par()
2272 || old_cursor.pos() != cursor.pos()) {
2273 // Only if the cursor has really moved
2275 if (old_cursor.pos() > 0
2276 && old_cursor.pos() < old_cursor.par()->size()
2277 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2278 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2279 old_cursor.par()->erase(old_cursor.pos() - 1);
2280 redoParagraphs(old_cursor, old_cursor.par()->next());
2282 #ifdef WITH_WARNINGS
2283 #warning This will not work anymore when we have multiple views of the same buffer
2284 // In this case, we will have to correct also the cursors held by
2285 // other bufferviews. It will probably be easier to do that in a more
2286 // automated way in LyXCursor code. (JMarc 26/09/2001)
2288 // correct all cursors held by the LyXText
2289 fixCursorAfterDelete(cursor, old_cursor);
2290 fixCursorAfterDelete(selection.cursor,
2292 fixCursorAfterDelete(selection.start,
2294 fixCursorAfterDelete(selection.end, old_cursor);
2295 fixCursorAfterDelete(last_sel_cursor,
2297 fixCursorAfterDelete(toggle_cursor, old_cursor);
2298 fixCursorAfterDelete(toggle_end_cursor,
2304 // don't delete anything if this is the ONLY paragraph!
2305 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2308 // Do not delete empty paragraphs with keepempty set.
2309 if (old_cursor.par()->layout()->keepempty)
2312 // only do our magic if we changed paragraph
2313 if (old_cursor.par() == cursor.par())
2316 // record if we have deleted a paragraph
2317 // we can't possibly have deleted a paragraph before this point
2318 bool deleted = false;
2320 if ((old_cursor.par()->empty()
2321 || (old_cursor.par()->size() == 1
2322 && old_cursor.par()->isLineSeparator(0)))) {
2323 // ok, we will delete anything
2324 LyXCursor tmpcursor;
2328 if (old_cursor.row()->previous()) {
2329 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2330 - old_cursor.row()->previous()->height());
2332 cursor = old_cursor; // that undo can restore the right cursor position
2333 Paragraph * endpar = old_cursor.par()->next();
2334 if (endpar && endpar->getDepth()) {
2335 while (endpar && endpar->getDepth()) {
2336 endpar = endpar->next();
2339 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2343 removeRow(old_cursor.row());
2344 if (ownerParagraph() == old_cursor.par()) {
2345 ownerParagraph(ownerParagraph()->next());
2348 delete old_cursor.par();
2350 /* Breakagain the next par. Needed because of
2351 * the parindent that can occur or dissappear.
2352 * The next row can change its height, if
2353 * there is another layout before */
2354 if (refresh_row->next()) {
2355 breakAgain(refresh_row->next());
2358 setHeightOfRow(refresh_row);
2360 Row * nextrow = old_cursor.row()->next();
2361 const_cast<LyXText *>(this)->postPaint(
2362 old_cursor.y() - old_cursor.row()->baseline());
2365 cursor = old_cursor; // that undo can restore the right cursor position
2366 Paragraph * endpar = old_cursor.par()->next();
2367 if (endpar && endpar->getDepth()) {
2368 while (endpar && endpar->getDepth()) {
2369 endpar = endpar->next();
2372 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2376 removeRow(old_cursor.row());
2378 if (ownerParagraph() == old_cursor.par()) {
2379 ownerParagraph(ownerParagraph()->next());
2382 delete old_cursor.par();
2384 /* Breakagain the next par. Needed because of
2385 the parindent that can occur or dissappear.
2386 The next row can change its height, if
2387 there is another layout before */
2389 breakAgain(nextrow);
2395 setCursorIntern(cursor.par(), cursor.pos());
2397 if (selection.cursor.par() == old_cursor.par()
2398 && selection.cursor.pos() == old_cursor.pos()) {
2399 // correct selection
2400 selection.cursor = cursor;
2404 if (old_cursor.par()->stripLeadingSpaces()) {
2405 redoParagraphs(old_cursor,
2406 old_cursor.par()->next());
2408 setCursorIntern(cursor.par(), cursor.pos());
2409 selection.cursor = cursor;
2416 Paragraph * LyXText::ownerParagraph() const
2419 return inset_owner->paragraph();
2421 return &*(bv_owner->buffer()->paragraphs.begin());
2425 void LyXText::ownerParagraph(Paragraph * p) const
2428 inset_owner->paragraph(p);
2430 bv_owner->buffer()->paragraphs.set(p);
2435 void LyXText::ownerParagraph(int id, Paragraph * p) const
2437 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2438 if (op && op->inInset()) {
2439 static_cast<InsetText *>(op->inInset())->paragraph(p);
2446 LyXText::text_status LyXText::status() const
2452 void LyXText::clearPaint()
2454 status_ = UNCHANGED;
2460 void LyXText::postChangedInDraw()
2462 status_ = CHANGED_IN_DRAW;
2466 void LyXText::postPaint(int start_y)
2468 text_status old = status_;
2470 status_ = NEED_MORE_REFRESH;
2473 if (old != UNCHANGED && refresh_y < start_y) {
2474 lyxerr << "Paint already pending from above" << endl;
2478 refresh_y = start_y;
2483 // We are an inset's lyxtext. Tell the top-level lyxtext
2484 // it needs to update the row we're in.
2486 LyXText * t = bv()->text;
2488 // FIXME: but what if this row is below ?
2489 if (!t->refresh_row) {
2490 t->refresh_row = t->cursor.row();
2491 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2496 // FIXME: we should probably remove this y parameter,
2497 // make refresh_y be 0, and use row->y etc.
2498 void LyXText::postRowPaint(Row * row, int start_y)
2500 if (status_ != UNCHANGED && refresh_y < start_y) {
2501 lyxerr << "Paint already pending from above" << endl;
2504 refresh_y = start_y;
2507 if (status_ == NEED_MORE_REFRESH)
2510 status_ = NEED_VERY_LITTLE_REFRESH;
2516 // We are an inset's lyxtext. Tell the top-level lyxtext
2517 // it needs to update the row we're in.
2519 LyXText * t = bv()->text;
2521 // FIXME: but what if this new row is above ?
2522 // Why the !t->refresh_row at all ?
2523 if (!t->refresh_row) {
2524 t->refresh_row = t->cursor.row();
2525 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2530 bool LyXText::isInInset() const
2532 // Sub-level has non-null bv owner and
2533 // non-null inset owner.
2534 return inset_owner != 0 && bv_owner != 0;
2538 int defaultRowHeight()
2540 LyXFont const font(LyXFont::ALL_SANE);
2541 return int(font_metrics::maxAscent(font)
2542 + font_metrics::maxDescent(font) * 1.5);