1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2001 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "frontends/LyXView.h"
17 #include "undo_funcs.h"
19 #include "bufferparams.h"
21 #include "BufferView.h"
22 #include "CutAndPaste.h"
23 #include "frontends/Painter.h"
24 #include "frontends/font_metrics.h"
28 #include "FloatList.h"
30 #include "ParagraphParameters.h"
33 #include "insets/inseterror.h"
34 #include "insets/insetbibitem.h"
35 #include "insets/insetspecialchar.h"
36 #include "insets/insettext.h"
37 #include "insets/insetfloat.h"
38 #include "insets/insetwrap.h"
40 #include "support/LAssert.h"
41 #include "support/textutils.h"
42 #include "support/lstrings.h"
44 #include "BoostFormat.h"
54 LyXText::LyXText(BufferView * bv)
55 : height(0), width(0), top_row_(0), top_row_offset_(0),
56 inset_owner(0), the_locking_inset(0), need_break_row(0),
57 bv_owner(bv), firstrow(0), lastrow(0)
63 LyXText::LyXText(BufferView * bv, InsetText * inset)
64 : height(0), width(0), top_row_(0), top_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 (top_row_ == row) {
336 top_row_ = row->next();
337 top_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, true);
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);
1799 float LyXText::getCursorX(Row * row,
1800 pos_type pos, pos_type last, bool boundary) const
1802 pos_type cursor_vpos = 0;
1804 float fill_separator;
1806 float fill_label_hfill;
1807 // This call HAS to be here because of the BidiTables!!!
1808 prepareToPrint(row, x, fill_separator, fill_hfill,
1811 if (last < row->pos())
1812 cursor_vpos = row->pos();
1813 else if (pos > last && !boundary)
1814 cursor_vpos = (row->par()->isRightToLeftPar(bv()->buffer()->params))
1815 ? row->pos() : last + 1;
1816 else if (pos > row->pos() &&
1817 (pos > last || boundary))
1818 /// Place cursor after char at (logical) position pos - 1
1819 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1820 ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1822 /// Place cursor before char at (logical) position pos
1823 cursor_vpos = (bidi_level(pos) % 2 == 0)
1824 ? log2vis(pos) : log2vis(pos) + 1;
1826 pos_type body_pos = row->par()->beginningOfBody();
1827 if ((body_pos > 0) &&
1828 ((body_pos-1 > last) ||
1829 !row->par()->isLineSeparator(body_pos - 1)))
1832 for (pos_type vpos = row->pos(); vpos < cursor_vpos; ++vpos) {
1833 pos_type pos = vis2log(vpos);
1834 if (body_pos > 0 && pos == body_pos - 1) {
1835 x += fill_label_hfill +
1836 font_metrics::width(
1837 row->par()->layout()->labelsep,
1838 getLabelFont(bv()->buffer(),
1840 if (row->par()->isLineSeparator(body_pos - 1))
1842 row->par(), body_pos - 1);
1844 if (row->hfillExpansion(pos)) {
1845 x += singleWidth(row->par(), pos);
1846 if (pos >= body_pos)
1849 x += fill_label_hfill;
1850 } else if (row->par()->isSeparator(pos)) {
1851 x += singleWidth(row->par(), pos);
1852 if (pos >= body_pos)
1853 x += fill_separator;
1855 x += singleWidth(row->par(), pos);
1861 void LyXText::setCursorIntern(Paragraph * par,
1862 pos_type pos, bool setfont, bool boundary)
1864 InsetText * it = static_cast<InsetText *>(par->inInset());
1866 if (it != inset_owner) {
1867 lyxerr[Debug::INSETS] << "InsetText is " << it
1869 << "inset_owner is "
1870 << inset_owner << endl;
1871 #ifdef WITH_WARNINGS
1872 #warning I believe this code is wrong. (Lgb)
1873 #warning Jürgen, have a look at this. (Lgb)
1874 #warning Hmmm, I guess you are right but we
1875 #warning should verify when this is needed
1877 // Jürgen, would you like to have a look?
1878 // I guess we need to move the outer cursor
1879 // and open and lock the inset (bla bla bla)
1880 // stuff I don't know... so can you have a look?
1882 // I moved the lyxerr stuff in here so we can see if
1883 // this is actually really needed and where!
1885 // it->getLyXText(bv())->setCursorIntern(bv(), par, pos, setfont, boundary);
1890 setCursor(cursor, par, pos, boundary);
1896 void LyXText::setCurrentFont()
1898 pos_type pos = cursor.pos();
1899 if (cursor.boundary() && pos > 0)
1903 if (pos == cursor.par()->size())
1905 else // potentional bug... BUG (Lgb)
1906 if (cursor.par()->isSeparator(pos)) {
1907 if (pos > cursor.row()->pos() &&
1908 bidi_level(pos) % 2 ==
1909 bidi_level(pos - 1) % 2)
1911 else if (pos + 1 < cursor.par()->size())
1917 cursor.par()->getFontSettings(bv()->buffer()->params, pos);
1918 real_current_font = getFont(bv()->buffer(), cursor.par(), pos);
1920 if (cursor.pos() == cursor.par()->size() &&
1921 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()) &&
1922 !cursor.boundary()) {
1923 Language const * lang =
1924 cursor.par()->getParLanguage(bv()->buffer()->params);
1925 current_font.setLanguage(lang);
1926 current_font.setNumber(LyXFont::OFF);
1927 real_current_font.setLanguage(lang);
1928 real_current_font.setNumber(LyXFont::OFF);
1933 // returns the column near the specified x-coordinate of the row
1934 // x is set to the real beginning of this column
1936 LyXText::getColumnNearX(Row * row, int & x,
1937 bool & boundary) const
1940 float fill_separator;
1942 float fill_label_hfill;
1944 prepareToPrint(row, tmpx, fill_separator,
1945 fill_hfill, fill_label_hfill);
1947 pos_type vc = row->pos();
1948 pos_type last = row->lastPrintablePos();
1951 LyXLayout_ptr const & layout = row->par()->layout();
1953 bool left_side = false;
1955 pos_type body_pos = row->par()->beginningOfBody();
1956 float last_tmpx = tmpx;
1959 (body_pos - 1 > last ||
1960 !row->par()->isLineSeparator(body_pos - 1)))
1963 // check for empty row
1964 if (!row->par()->size()) {
1969 while (vc <= last && tmpx <= x) {
1972 if (body_pos > 0 && c == body_pos-1) {
1973 tmpx += fill_label_hfill +
1974 font_metrics::width(layout->labelsep,
1975 getLabelFont(bv()->buffer(), row->par()));
1976 if (row->par()->isLineSeparator(body_pos - 1))
1977 tmpx -= singleWidth(row->par(), body_pos-1);
1980 if (row->hfillExpansion(c)) {
1981 tmpx += singleWidth(row->par(), c);
1985 tmpx += fill_label_hfill;
1986 } else if (row->par()->isSeparator(c)) {
1987 tmpx += singleWidth(row->par(), c);
1989 tmpx+= fill_separator;
1991 tmpx += singleWidth(row->par(), c);
1996 if ((tmpx + last_tmpx) / 2 > x) {
2001 if (vc > last + 1) // This shouldn't happen.
2005 bool const lastrow = lyxrc.rtl_support // This is not needed, but gives
2006 // some speedup if rtl_support=false
2007 && (!row->next() || row->next()->par() != row->par());
2008 bool const rtl = (lastrow)
2009 ? row->par()->isRightToLeftPar(bv()->buffer()->params)
2010 : false; // If lastrow is false, we don't need to compute
2011 // the value of rtl.
2014 ((rtl && left_side && vc == row->pos() && x < tmpx - 5) ||
2015 (!rtl && !left_side && vc == last + 1 && x > tmpx + 5)))
2017 else if (vc == row->pos()) {
2019 if (bidi_level(c) % 2 == 1)
2022 c = vis2log(vc - 1);
2023 bool const rtl = (bidi_level(c) % 2 == 1);
2024 if (left_side == rtl) {
2026 boundary = isBoundary(bv()->buffer(), row->par(), c);
2030 if (row->pos() <= last && c > last
2031 && row->par()->isNewline(last)) {
2032 if (bidi_level(last) % 2 == 0)
2033 tmpx -= singleWidth(row->par(), last);
2035 tmpx += singleWidth(row->par(), last);
2045 void LyXText::setCursorFromCoordinates(int x, int y)
2047 LyXCursor old_cursor = cursor;
2049 setCursorFromCoordinates(cursor, x, y);
2051 deleteEmptyParagraphMechanism(old_cursor);
2058 * return true if the cursor given is at the end of a row,
2059 * and the next row is filled by an inset that spans an entire
2062 bool beforeFullRowInset(Row & row, LyXCursor & cur) {
2065 Row const & next = *row.next();
2067 if (next.pos() != cur.pos() || next.par() != cur.par())
2069 if (!cur.par()->isInset(cur.pos()))
2071 Inset const * inset = cur.par()->getInset(cur.pos());
2072 if (inset->needFullRow() || inset->display())
2079 void LyXText::setCursorFromCoordinates(LyXCursor & cur,
2082 // Get the row first.
2084 Row * row = getRowNearY(y);
2086 pos_type const column = getColumnNearX(row, x, bound);
2087 cur.par(row->par());
2088 cur.pos(row->pos() + column);
2090 cur.y(y + row->baseline());
2093 if (beforeFullRowInset(*row, cur)) {
2094 pos_type last = row->lastPrintablePos();
2095 float x = getCursorX(row->next(), cur.pos(), last, bound);
2097 cur.iy(y + row->height() + row->next()->baseline());
2098 cur.irow(row->next());
2104 cur.boundary(bound);
2108 void LyXText::cursorLeft(bool internal)
2110 if (cursor.pos() > 0) {
2111 bool boundary = cursor.boundary();
2112 setCursor(cursor.par(), cursor.pos() - 1, true, false);
2113 if (!internal && !boundary &&
2114 isBoundary(bv()->buffer(), cursor.par(), cursor.pos() + 1))
2115 setCursor(cursor.par(), cursor.pos() + 1, true, true);
2116 } else if (cursor.par()->previous()) { // steps into the above paragraph.
2117 Paragraph * par = cursor.par()->previous();
2118 setCursor(par, par->size());
2123 void LyXText::cursorRight(bool internal)
2125 if (!internal && cursor.boundary() &&
2126 !cursor.par()->isNewline(cursor.pos()))
2127 setCursor(cursor.par(), cursor.pos(), true, false);
2128 else if (cursor.pos() < cursor.par()->size()) {
2129 setCursor(cursor.par(), cursor.pos() + 1, true, false);
2131 isBoundary(bv()->buffer(), cursor.par(), cursor.pos()))
2132 setCursor(cursor.par(), cursor.pos(), true, true);
2133 } else if (cursor.par()->next())
2134 setCursor(cursor.par()->next(), 0);
2138 void LyXText::cursorUp(bool selecting)
2141 int x = cursor.x_fix();
2142 int y = cursor.y() - cursor.row()->baseline() - 1;
2143 setCursorFromCoordinates(x, y);
2146 int y1 = cursor.iy() - topy;
2149 Inset * inset_hit = checkInsetHit(x, y1);
2150 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2151 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2155 setCursorFromCoordinates(bv(), cursor.x_fix(),
2156 cursor.y() - cursor.row()->baseline() - 1);
2161 void LyXText::cursorDown(bool selecting)
2164 int x = cursor.x_fix();
2165 int y = cursor.y() - cursor.row()->baseline() +
2166 cursor.row()->height() + 1;
2167 setCursorFromCoordinates(x, y);
2168 if (!selecting && cursor.row() == cursor.irow()) {
2170 int y1 = cursor.iy() - topy;
2173 Inset * inset_hit = checkInsetHit(x, y1);
2174 if (inset_hit && isHighlyEditableInset(inset_hit)) {
2175 inset_hit->edit(bv(), x, y - (y2 - y1), mouse_button::none);
2179 setCursorFromCoordinates(bv(), cursor.x_fix(),
2180 cursor.y() - cursor.row()->baseline()
2181 + cursor.row()->height() + 1);
2186 void LyXText::cursorUpParagraph()
2188 if (cursor.pos() > 0) {
2189 setCursor(cursor.par(), 0);
2191 else if (cursor.par()->previous()) {
2192 setCursor(cursor.par()->previous(), 0);
2197 void LyXText::cursorDownParagraph()
2199 if (cursor.par()->next()) {
2200 setCursor(cursor.par()->next(), 0);
2202 setCursor(cursor.par(), cursor.par()->size());
2206 // fix the cursor `cur' after a characters has been deleted at `where'
2207 // position. Called by deleteEmptyParagraphMechanism
2208 void LyXText::fixCursorAfterDelete(LyXCursor & cur,
2209 LyXCursor const & where)
2211 // if cursor is not in the paragraph where the delete occured,
2213 if (cur.par() != where.par())
2216 // if cursor position is after the place where the delete occured,
2218 if (cur.pos() > where.pos())
2219 cur.pos(cur.pos()-1);
2221 // check also if we don't want to set the cursor on a spot behind the
2222 // pagragraph because we erased the last character.
2223 if (cur.pos() > cur.par()->size())
2224 cur.pos(cur.par()->size());
2226 // recompute row et al. for this cursor
2227 setCursor(cur, cur.par(), cur.pos(), cur.boundary());
2231 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
2233 // Would be wrong to delete anything if we have a selection.
2234 if (selection.set())
2237 // We allow all kinds of "mumbo-jumbo" when freespacing.
2238 if (old_cursor.par()->layout()->free_spacing
2239 || old_cursor.par()->isFreeSpacing()) {
2243 /* Ok I'll put some comments here about what is missing.
2244 I have fixed BackSpace (and thus Delete) to not delete
2245 double-spaces automagically. I have also changed Cut,
2246 Copy and Paste to hopefully do some sensible things.
2247 There are still some small problems that can lead to
2248 double spaces stored in the document file or space at
2249 the beginning of paragraphs. This happens if you have
2250 the cursor betwenn to spaces and then save. Or if you
2251 cut and paste and the selection have a space at the
2252 beginning and then save right after the paste. I am
2253 sure none of these are very hard to fix, but I will
2254 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
2255 that I can get some feedback. (Lgb)
2258 // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
2259 // delete the LineSeparator.
2262 // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
2263 // delete the LineSeparator.
2266 // If the pos around the old_cursor were spaces, delete one of them.
2267 if (old_cursor.par() != cursor.par()
2268 || old_cursor.pos() != cursor.pos()) {
2269 // Only if the cursor has really moved
2271 if (old_cursor.pos() > 0
2272 && old_cursor.pos() < old_cursor.par()->size()
2273 && old_cursor.par()->isLineSeparator(old_cursor.pos())
2274 && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
2275 old_cursor.par()->erase(old_cursor.pos() - 1);
2276 redoParagraphs(old_cursor, old_cursor.par()->next());
2278 #ifdef WITH_WARNINGS
2279 #warning This will not work anymore when we have multiple views of the same buffer
2280 // In this case, we will have to correct also the cursors held by
2281 // other bufferviews. It will probably be easier to do that in a more
2282 // automated way in LyXCursor code. (JMarc 26/09/2001)
2284 // correct all cursors held by the LyXText
2285 fixCursorAfterDelete(cursor, old_cursor);
2286 fixCursorAfterDelete(selection.cursor,
2288 fixCursorAfterDelete(selection.start,
2290 fixCursorAfterDelete(selection.end, old_cursor);
2291 fixCursorAfterDelete(last_sel_cursor,
2293 fixCursorAfterDelete(toggle_cursor, old_cursor);
2294 fixCursorAfterDelete(toggle_end_cursor,
2300 // don't delete anything if this is the ONLY paragraph!
2301 if (!old_cursor.par()->next() && !old_cursor.par()->previous())
2304 // Do not delete empty paragraphs with keepempty set.
2305 if (old_cursor.par()->layout()->keepempty)
2308 // only do our magic if we changed paragraph
2309 if (old_cursor.par() == cursor.par())
2312 // record if we have deleted a paragraph
2313 // we can't possibly have deleted a paragraph before this point
2314 bool deleted = false;
2316 if ((old_cursor.par()->empty()
2317 || (old_cursor.par()->size() == 1
2318 && old_cursor.par()->isLineSeparator(0)))) {
2319 // ok, we will delete anything
2320 LyXCursor tmpcursor;
2324 if (old_cursor.row()->previous()) {
2325 const_cast<LyXText *>(this)->postPaint(old_cursor.y() - old_cursor.row()->baseline()
2326 - old_cursor.row()->previous()->height());
2328 cursor = old_cursor; // that undo can restore the right cursor position
2329 Paragraph * endpar = old_cursor.par()->next();
2330 if (endpar && endpar->getDepth()) {
2331 while (endpar && endpar->getDepth()) {
2332 endpar = endpar->next();
2335 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2339 removeRow(old_cursor.row());
2340 if (ownerParagraph() == old_cursor.par()) {
2341 ownerParagraph(ownerParagraph()->next());
2344 delete old_cursor.par();
2346 /* Breakagain the next par. Needed because of
2347 * the parindent that can occur or dissappear.
2348 * The next row can change its height, if
2349 * there is another layout before */
2350 if (refresh_row->next()) {
2351 breakAgain(refresh_row->next());
2354 setHeightOfRow(refresh_row);
2356 Row * nextrow = old_cursor.row()->next();
2357 const_cast<LyXText *>(this)->postPaint(
2358 old_cursor.y() - old_cursor.row()->baseline());
2361 cursor = old_cursor; // that undo can restore the right cursor position
2362 Paragraph * endpar = old_cursor.par()->next();
2363 if (endpar && endpar->getDepth()) {
2364 while (endpar && endpar->getDepth()) {
2365 endpar = endpar->next();
2368 setUndo(bv(), Undo::DELETE, old_cursor.par(), endpar);
2372 removeRow(old_cursor.row());
2374 if (ownerParagraph() == old_cursor.par()) {
2375 ownerParagraph(ownerParagraph()->next());
2378 delete old_cursor.par();
2380 /* Breakagain the next par. Needed because of
2381 the parindent that can occur or dissappear.
2382 The next row can change its height, if
2383 there is another layout before */
2385 breakAgain(nextrow);
2391 setCursorIntern(cursor.par(), cursor.pos());
2393 if (selection.cursor.par() == old_cursor.par()
2394 && selection.cursor.pos() == old_cursor.pos()) {
2395 // correct selection
2396 selection.cursor = cursor;
2400 if (old_cursor.par()->stripLeadingSpaces()) {
2401 redoParagraphs(old_cursor,
2402 old_cursor.par()->next());
2404 setCursorIntern(cursor.par(), cursor.pos());
2405 selection.cursor = cursor;
2412 Paragraph * LyXText::ownerParagraph() const
2415 return inset_owner->paragraph();
2417 return &*(bv_owner->buffer()->paragraphs.begin());
2421 void LyXText::ownerParagraph(Paragraph * p) const
2424 inset_owner->paragraph(p);
2426 bv_owner->buffer()->paragraphs.set(p);
2431 void LyXText::ownerParagraph(int id, Paragraph * p) const
2433 Paragraph * op = bv_owner->buffer()->getParFromID(id);
2434 if (op && op->inInset()) {
2435 static_cast<InsetText *>(op->inInset())->paragraph(p);
2442 LyXText::text_status LyXText::status() const
2448 void LyXText::clearPaint()
2450 status_ = UNCHANGED;
2456 void LyXText::postChangedInDraw()
2458 status_ = CHANGED_IN_DRAW;
2462 void LyXText::postPaint(int start_y)
2464 text_status old = status_;
2466 status_ = NEED_MORE_REFRESH;
2469 if (old != UNCHANGED && refresh_y < start_y) {
2470 lyxerr << "Paint already pending from above" << endl;
2474 refresh_y = start_y;
2479 // We are an inset's lyxtext. Tell the top-level lyxtext
2480 // it needs to update the row we're in.
2482 LyXText * t = bv()->text;
2484 // FIXME: but what if this row is below ?
2485 if (!t->refresh_row) {
2486 t->refresh_row = t->cursor.row();
2487 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2492 // FIXME: we should probably remove this y parameter,
2493 // make refresh_y be 0, and use row->y etc.
2494 void LyXText::postRowPaint(Row * row, int start_y)
2496 if (status_ != UNCHANGED && refresh_y < start_y) {
2497 lyxerr << "Paint already pending from above" << endl;
2500 refresh_y = start_y;
2503 if (status_ == NEED_MORE_REFRESH)
2506 status_ = NEED_VERY_LITTLE_REFRESH;
2512 // We are an inset's lyxtext. Tell the top-level lyxtext
2513 // it needs to update the row we're in.
2515 LyXText * t = bv()->text;
2517 // FIXME: but what if this new row is above ?
2518 // Why the !t->refresh_row at all ?
2519 if (!t->refresh_row) {
2520 t->refresh_row = t->cursor.row();
2521 t->refresh_y = t->cursor.y() - t->cursor.row()->baseline();
2526 bool LyXText::isInInset() const
2528 // Sub-level has non-null bv owner and
2529 // non-null inset owner.
2530 return inset_owner != 0 && bv_owner != 0;
2534 int defaultRowHeight()
2536 LyXFont const font(LyXFont::ALL_SANE);
2537 return int(font_metrics::maxAscent(font)
2538 + font_metrics::maxDescent(font) * 1.5);