3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
30 #include "bufferview_funcs.h"
32 #include "CoordCache.h"
34 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
37 #include "ErrorList.h"
38 #include "FuncRequest.h"
45 #include "Paragraph.h"
46 #include "TextMetrics.h"
47 #include "paragraph_funcs.h"
48 #include "ParagraphParameters.h"
49 #include "ParIterator.h"
51 #include "ServerSocket.h"
55 #include "frontends/FontMetrics.h"
57 #include "insets/InsetEnvironment.h"
59 #include "mathed/InsetMathHull.h"
61 #include "support/textutils.h"
63 #include <boost/current_function.hpp>
71 using std::ostringstream;
78 : current_font(Font::ALL_INHERIT),
79 background_color_(Color::background),
84 bool Text::isMainText(Buffer const & buffer) const
86 return &buffer.text() == this;
90 //takes screen x,y coordinates
91 Inset * Text::checkInsetHit(BufferView & bv, int x, int y)
93 pit_type pit = getPitNearY(bv, y);
94 BOOST_ASSERT(pit != -1);
96 Paragraph const & par = pars_[pit];
99 << BOOST_CURRENT_FUNCTION
104 InsetList::const_iterator iit = par.insetlist.begin();
105 InsetList::const_iterator iend = par.insetlist.end();
106 for (; iit != iend; ++iit) {
107 Inset * inset = iit->inset;
110 << BOOST_CURRENT_FUNCTION
111 << ": examining inset " << inset << endl;
113 if (bv.coordCache().getInsets().has(inset))
115 << BOOST_CURRENT_FUNCTION
116 << ": xo: " << inset->xo(bv) << "..."
117 << inset->xo(bv) + inset->width()
118 << " yo: " << inset->yo(bv) - inset->ascent()
120 << inset->yo(bv) + inset->descent()
124 << BOOST_CURRENT_FUNCTION
125 << ": inset has no cached position" << endl;
127 if (inset->covers(bv, x, y)) {
129 << BOOST_CURRENT_FUNCTION
130 << ": Hit inset: " << inset << endl;
135 << BOOST_CURRENT_FUNCTION
136 << ": No inset hit. " << endl;
142 // Gets the fully instantiated font at a given position in a paragraph
143 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
144 // The difference is that this one is used for displaying, and thus we
145 // are allowed to make cosmetic improvements. For instance make footnotes
147 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
148 pos_type const pos) const
150 BOOST_ASSERT(pos >= 0);
152 Layout_ptr const & layout = par.layout();
156 BufferParams const & params = buffer.params();
157 pos_type const body_pos = par.beginOfBody();
159 // We specialize the 95% common case:
160 if (!par.getDepth()) {
161 Font f = par.getFontSettings(params, pos);
162 if (!isMainText(buffer))
163 applyOuterFont(buffer, f);
166 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
167 lf = layout->labelfont;
168 rlf = layout->reslabelfont;
171 rlf = layout->resfont;
173 // In case the default family has been customized
174 if (lf.family() == Font::INHERIT_FAMILY)
175 rlf.setFamily(params.getFont().family());
176 return f.realize(rlf);
179 // The uncommon case need not be optimized as much
182 layoutfont = layout->labelfont;
184 layoutfont = layout->font;
186 Font font = par.getFontSettings(params, pos);
187 font.realize(layoutfont);
189 if (!isMainText(buffer))
190 applyOuterFont(buffer, font);
192 // Find the pit value belonging to paragraph. This will not break
193 // even if pars_ would not be a vector anymore.
194 // Performance appears acceptable.
196 pit_type pit = pars_.size();
197 for (pit_type it = 0; it < pit; ++it)
198 if (&pars_[it] == &par) {
202 // Realize against environment font information
203 // NOTE: the cast to pit_type should be removed when pit_type
204 // changes to a unsigned integer.
205 if (pit < pit_type(pars_.size()))
206 font.realize(outerFont(pit, pars_));
208 // Realize with the fonts of lesser depth.
209 font.realize(params.getFont());
214 // There are currently two font mechanisms in LyX:
215 // 1. The font attributes in a lyxtext, and
216 // 2. The inset-specific font properties, defined in an inset's
217 // metrics() and draw() methods and handed down the inset chain through
218 // the pi/mi parameters, and stored locally in a lyxtext in font_.
219 // This is where the two are integrated in the final fully realized
221 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
223 lf.reduce(buffer.params().getFont());
225 lf.setLanguage(font.language());
230 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
232 Layout_ptr const & layout = pars_[pit].layout();
234 if (!pars_[pit].getDepth()) {
235 Font lf = layout->resfont;
236 // In case the default family has been customized
237 if (layout->font.family() == Font::INHERIT_FAMILY)
238 lf.setFamily(buffer.params().getFont().family());
242 Font font = layout->font;
243 // Realize with the fonts of lesser depth.
244 //font.realize(outerFont(pit, paragraphs()));
245 font.realize(buffer.params().getFont());
251 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
253 Layout_ptr const & layout = par.layout();
255 if (!par.getDepth()) {
256 Font lf = layout->reslabelfont;
257 // In case the default family has been customized
258 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
259 lf.setFamily(buffer.params().getFont().family());
263 Font font = layout->labelfont;
264 // Realize with the fonts of lesser depth.
265 font.realize(buffer.params().getFont());
271 void Text::setCharFont(Buffer const & buffer, pit_type pit,
272 pos_type pos, Font const & fnt)
275 Layout_ptr const & layout = pars_[pit].layout();
277 // Get concrete layout font to reduce against
280 if (pos < pars_[pit].beginOfBody())
281 layoutfont = layout->labelfont;
283 layoutfont = layout->font;
285 // Realize against environment font information
286 if (pars_[pit].getDepth()) {
288 while (!layoutfont.resolved() &&
289 tp != pit_type(paragraphs().size()) &&
290 pars_[tp].getDepth()) {
291 tp = outerHook(tp, paragraphs());
292 if (tp != pit_type(paragraphs().size()))
293 layoutfont.realize(pars_[tp].layout()->font);
297 // Inside inset, apply the inset's font attributes if any
299 if (!isMainText(buffer))
300 layoutfont.realize(font_);
302 layoutfont.realize(buffer.params().getFont());
304 // Now, reduce font against full layout font
305 font.reduce(layoutfont);
307 pars_[pit].setFont(pos, font);
311 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
312 pos_type pos, Font const & font, bool toggleall)
314 BOOST_ASSERT(pars_[pit].isInset(pos) &&
315 pars_[pit].getInset(pos)->noFontChange());
317 Inset * const inset = pars_[pit].getInset(pos);
318 DocIterator dit = doc_iterator_begin(*inset);
319 // start of the last cell
320 DocIterator end = dit;
321 end.idx() = end.lastidx();
324 Text * text = dit.text();
325 Inset * cell = dit.realInset();
327 DocIterator cellbegin = doc_iterator_begin(*cell);
328 // last position of the cell
329 DocIterator cellend = cellbegin;
330 cellend.pit() = cellend.lastpit();
331 cellend.pos() = cellend.lastpos();
332 text->setFont(buffer, cellbegin, cellend, font, toggleall);
341 // return past-the-last paragraph influenced by a layout change on pit
342 pit_type Text::undoSpan(pit_type pit)
344 pit_type end = paragraphs().size();
345 pit_type nextpit = pit + 1;
348 //because of parindents
349 if (!pars_[pit].getDepth())
350 return boost::next(nextpit);
351 //because of depth constrains
352 for (; nextpit != end; ++pit, ++nextpit) {
353 if (!pars_[pit].getDepth())
360 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
361 string const & layout)
363 BOOST_ASSERT(start != end);
365 BufferParams const & bufparams = buffer.params();
366 Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
368 for (pit_type pit = start; pit != end; ++pit) {
369 Paragraph & par = pars_[pit];
370 par.applyLayout(lyxlayout);
371 if (lyxlayout->margintype == MARGIN_MANUAL)
372 par.setLabelWidthString(par.translateIfPossible(
373 lyxlayout->labelstring(), buffer.params()));
378 // set layout over selection and make a total rebreak of those paragraphs
379 void Text::setLayout(Cursor & cur, string const & layout)
381 BOOST_ASSERT(this == cur.text());
382 // special handling of new environment insets
383 BufferView & bv = cur.bv();
384 BufferParams const & params = bv.buffer()->params();
385 Layout_ptr const & lyxlayout = params.getTextClass()[layout];
386 if (lyxlayout->is_environment) {
387 // move everything in a new environment inset
388 LYXERR(Debug::DEBUG) << "setting layout " << layout << endl;
389 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
390 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
391 lyx::dispatch(FuncRequest(LFUN_CUT));
392 Inset * inset = new InsetEnvironment(params, layout);
393 insertInset(cur, inset);
394 //inset->edit(cur, true);
395 //lyx::dispatch(FuncRequest(LFUN_PASTE));
399 pit_type start = cur.selBegin().pit();
400 pit_type end = cur.selEnd().pit() + 1;
401 pit_type undopit = undoSpan(end - 1);
402 recUndo(cur, start, undopit - 1);
403 setLayout(cur.buffer(), start, end, layout);
404 updateLabels(cur.buffer());
408 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
409 Paragraph const & par, int max_depth)
411 if (par.layout()->labeltype == LABEL_BIBLIO)
413 int const depth = par.params().depth();
414 if (type == Text::INC_DEPTH && depth < max_depth)
416 if (type == Text::DEC_DEPTH && depth > 0)
422 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
424 BOOST_ASSERT(this == cur.text());
425 // this happens when selecting several cells in tabular (bug 2630)
426 if (cur.selBegin().idx() != cur.selEnd().idx())
429 pit_type const beg = cur.selBegin().pit();
430 pit_type const end = cur.selEnd().pit() + 1;
431 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
433 for (pit_type pit = beg; pit != end; ++pit) {
434 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
436 max_depth = pars_[pit].getMaxDepthAfter();
442 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
444 BOOST_ASSERT(this == cur.text());
445 pit_type const beg = cur.selBegin().pit();
446 pit_type const end = cur.selEnd().pit() + 1;
447 recordUndoSelection(cur);
448 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
450 for (pit_type pit = beg; pit != end; ++pit) {
451 Paragraph & par = pars_[pit];
452 if (lyx::changeDepthAllowed(type, par, max_depth)) {
453 int const depth = par.params().depth();
454 if (type == INC_DEPTH)
455 par.params().depth(depth + 1);
457 par.params().depth(depth - 1);
459 max_depth = par.getMaxDepthAfter();
461 // this handles the counter labels, and also fixes up
462 // depth values for follow-on (child) paragraphs
463 updateLabels(cur.buffer());
467 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
469 BOOST_ASSERT(this == cur.text());
470 // Set the current_font
471 // Determine basis font
473 pit_type pit = cur.pit();
474 if (cur.pos() < pars_[pit].beginOfBody())
475 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
477 layoutfont = getLayoutFont(cur.buffer(), pit);
479 // Update current font
480 real_current_font.update(font,
481 cur.buffer().params().language,
484 // Reduce to implicit settings
485 current_font = real_current_font;
486 current_font.reduce(layoutfont);
487 // And resolve it completely
488 real_current_font.realize(layoutfont);
490 // if there is no selection that's all we need to do
491 if (!cur.selection())
494 // Ok, we have a selection.
495 recordUndoSelection(cur);
497 setFont(cur.buffer(), cur.selectionBegin(), cur.selectionEnd(), font,
502 void Text::setFont(Buffer const & buffer, DocIterator const & begin,
503 DocIterator const & end, Font const & font,
506 // Don't use forwardChar here as ditend might have
507 // pos() == lastpos() and forwardChar would miss it.
508 // Can't use forwardPos either as this descends into
510 Language const * language = buffer.params().language;
511 for (DocIterator dit = begin; dit != end; dit.forwardPosNoDescend()) {
512 if (dit.pos() != dit.lastpos()) {
513 pit_type const pit = dit.pit();
514 pos_type const pos = dit.pos();
515 if (pars_[pit].isInset(pos) &&
516 pars_[pit].getInset(pos)->noFontChange())
517 // We need to propagate the font change to all
518 // text cells of the inset (bug 1973).
519 // FIXME: This should change, see documentation
520 // of noFontChange in Inset.h
521 setInsetFont(buffer, pit, pos, font, toggleall);
522 Font f = getFont(buffer, dit.paragraph(), pos);
523 f.update(font, language, toggleall);
524 setCharFont(buffer, pit, pos, f);
530 // the cursor set functions have a special mechanism. When they
531 // realize you left an empty paragraph, they will delete it.
533 bool Text::cursorHome(Cursor & cur)
535 BOOST_ASSERT(this == cur.text());
536 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
537 Row const & row = pm.getRow(cur.pos(),cur.boundary());
538 return setCursor(cur, cur.pit(), row.pos());
542 bool Text::cursorEnd(Cursor & cur)
544 BOOST_ASSERT(this == cur.text());
545 // if not on the last row of the par, put the cursor before
546 // the final space exept if I have a spanning inset or one string
547 // is so long that we force a break.
548 pos_type end = cur.textRow().endpos();
550 // empty text, end-1 is no valid position
552 bool boundary = false;
553 if (end != cur.lastpos()) {
554 if (!cur.paragraph().isLineSeparator(end-1)
555 && !cur.paragraph().isNewline(end-1))
560 return setCursor(cur, cur.pit(), end, true, boundary);
564 bool Text::cursorTop(Cursor & cur)
566 BOOST_ASSERT(this == cur.text());
567 return setCursor(cur, 0, 0);
571 bool Text::cursorBottom(Cursor & cur)
573 BOOST_ASSERT(this == cur.text());
574 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
578 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
580 BOOST_ASSERT(this == cur.text());
581 // If the mask is completely neutral, tell user
582 if (font == Font(Font::ALL_IGNORE)) {
583 // Could only happen with user style
584 cur.message(_("No font change defined."));
588 // Try implicit word selection
589 // If there is a change in the language the implicit word selection
591 CursorSlice resetCursor = cur.top();
592 bool implicitSelection =
593 font.language() == ignore_language
594 && font.number() == Font::IGNORE
595 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
598 setFont(cur, font, toggleall);
600 // Implicit selections are cleared afterwards
601 // and cursor is set to the original position.
602 if (implicitSelection) {
603 cur.clearSelection();
604 cur.top() = resetCursor;
610 docstring Text::getStringToIndex(Cursor const & cur)
612 BOOST_ASSERT(this == cur.text());
616 idxstring = cur.selectionAsString(false);
618 // Try implicit word selection. If there is a change
619 // in the language the implicit word selection is
622 selectWord(tmpcur, PREVIOUS_WORD);
624 if (!tmpcur.selection())
625 cur.message(_("Nothing to index!"));
626 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
627 cur.message(_("Cannot index more than one paragraph!"));
629 idxstring = tmpcur.selectionAsString(false);
636 void Text::setParagraph(Cursor & cur,
637 Spacing const & spacing, LyXAlignment align,
638 docstring const & labelwidthstring, bool noindent)
640 BOOST_ASSERT(cur.text());
641 // make sure that the depth behind the selection are restored, too
642 pit_type undopit = undoSpan(cur.selEnd().pit());
643 recUndo(cur, cur.selBegin().pit(), undopit - 1);
645 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
647 Paragraph & par = pars_[pit];
648 ParagraphParameters & params = par.params();
649 params.spacing(spacing);
651 // does the layout allow the new alignment?
652 Layout_ptr const & layout = par.layout();
654 if (align == LYX_ALIGN_LAYOUT)
655 align = layout->align;
656 if (align & layout->alignpossible) {
657 if (align == layout->align)
658 params.align(LYX_ALIGN_LAYOUT);
662 par.setLabelWidthString(labelwidthstring);
663 params.noindent(noindent);
668 // this really should just insert the inset and not move the cursor.
669 void Text::insertInset(Cursor & cur, Inset * inset)
671 BOOST_ASSERT(this == cur.text());
673 cur.paragraph().insertInset(cur.pos(), inset,
674 Change(cur.buffer().params().trackChanges ?
675 Change::INSERTED : Change::UNCHANGED));
679 // needed to insert the selection
680 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
682 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
683 current_font, str, autoBreakRows_);
687 // turn double CR to single CR, others are converted into one
688 // blank. Then insertStringAsLines is called
689 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
691 docstring linestr = str;
692 bool newline_inserted = false;
694 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
695 if (linestr[i] == '\n') {
696 if (newline_inserted) {
697 // we know that \r will be ignored by
698 // insertStringAsLines. Of course, it is a dirty
699 // trick, but it works...
700 linestr[i - 1] = '\r';
704 newline_inserted = true;
706 } else if (isPrintable(linestr[i])) {
707 newline_inserted = false;
710 insertStringAsLines(cur, linestr);
714 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
715 bool setfont, bool boundary)
718 setCursorIntern(cur, par, pos, setfont, boundary);
719 return cur.bv().checkDepm(cur, old);
723 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
725 BOOST_ASSERT(par != int(paragraphs().size()));
729 // now some strict checking
730 Paragraph & para = getPar(par);
732 // None of these should happen, but we're scaredy-cats
734 lyxerr << "dont like -1" << endl;
738 if (pos > para.size()) {
739 lyxerr << "dont like 1, pos: " << pos
740 << " size: " << para.size()
741 << " par: " << par << endl;
747 void Text::setCursorIntern(Cursor & cur,
748 pit_type par, pos_type pos, bool setfont, bool boundary)
750 BOOST_ASSERT(this == cur.text());
751 cur.boundary(boundary);
752 setCursor(cur.top(), par, pos);
758 void Text::setCurrentFont(Cursor & cur)
760 BOOST_ASSERT(this == cur.text());
761 pos_type pos = cur.pos();
762 Paragraph & par = cur.paragraph();
764 // are we behind previous char in fact? -> go to that char
765 if (pos > 0 && cur.boundary())
768 // find position to take the font from
770 // paragraph end? -> font of last char
771 if (pos == cur.lastpos())
773 // on space? -> look at the words in front of space
774 else if (pos > 0 && par.isSeparator(pos)) {
775 // abc| def -> font of c
776 // abc |[WERBEH], i.e. boundary==true -> font of c
777 // abc [WERBEH]| def, font of the space
778 if (!isRTLBoundary(cur.buffer(), par, pos))
784 BufferParams const & bufparams = cur.buffer().params();
785 current_font = par.getFontSettings(bufparams, pos);
786 real_current_font = getFont(cur.buffer(), par, pos);
788 // special case for paragraph end
789 if (cur.pos() == cur.lastpos()
790 && isRTLBoundary(cur.buffer(), par, cur.pos())
791 && !cur.boundary()) {
792 Language const * lang = par.getParLanguage(bufparams);
793 current_font.setLanguage(lang);
794 current_font.setNumber(Font::OFF);
795 real_current_font.setLanguage(lang);
796 real_current_font.setNumber(Font::OFF);
800 // y is screen coordinate
801 pit_type Text::getPitNearY(BufferView & bv, int y) const
803 BOOST_ASSERT(!paragraphs().empty());
804 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
805 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
807 << BOOST_CURRENT_FUNCTION
808 << ": y: " << y << " cache size: " << cc.size()
811 // look for highest numbered paragraph with y coordinate less than given y
814 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
815 CoordCache::InnerParPosCache::const_iterator et = cc.end();
816 CoordCache::InnerParPosCache::const_iterator last = et; last--;
818 TextMetrics & tm = bv.textMetrics(this);
819 ParagraphMetrics const & pm = tm.parMetrics(it->first);
821 // If we are off-screen (before the visible part)
823 // and even before the first paragraph in the cache.
824 && y < it->second.y_ - int(pm.ascent())) {
825 // and we are not at the first paragraph in the inset.
828 // then this is the paragraph we are looking for.
830 // rebreak it and update the CoordCache.
831 tm.redoParagraph(pit);
832 bv.coordCache().parPos()[this][pit] =
833 Point(0, it->second.y_ - pm.descent());
837 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
839 // If we are off-screen (after the visible part)
840 if (y > bv.workHeight()
841 // and even after the first paragraph in the cache.
842 && y >= last->second.y_ + int(pm_last.descent())) {
843 pit = last->first + 1;
844 // and we are not at the last paragraph in the inset.
845 if (pit == int(pars_.size()))
847 // then this is the paragraph we are looking for.
848 // rebreak it and update the CoordCache.
849 tm.redoParagraph(pit);
850 bv.coordCache().parPos()[this][pit] =
851 Point(0, last->second.y_ + pm_last.ascent());
855 for (; it != et; ++it) {
857 << BOOST_CURRENT_FUNCTION
858 << " examining: pit: " << it->first
859 << " y: " << it->second.y_
862 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
864 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
871 << BOOST_CURRENT_FUNCTION
872 << ": found best y: " << yy << " for pit: " << pit
879 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
881 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
883 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
884 BOOST_ASSERT(!pm.rows().empty());
885 RowList::const_iterator rit = pm.rows().begin();
886 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
887 for (; rit != rlast; yy += rit->height(), ++rit)
888 if (yy + rit->height() > y)
894 // x,y are absolute screen coordinates
895 // sets cursor recursively descending into nested editable insets
896 Inset * Text::editXY(Cursor & cur, int x, int y)
898 if (lyxerr.debugging(Debug::WORKAREA)) {
899 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
900 cur.bv().coordCache().dump();
902 pit_type pit = getPitNearY(cur.bv(), y);
903 BOOST_ASSERT(pit != -1);
905 Row const & row = getRowNearY(cur.bv(), y, pit);
908 TextMetrics const & tm = cur.bv().textMetrics(this);
909 int xx = x; // is modified by getColumnNearX
910 pos_type const pos = row.pos()
911 + tm.getColumnNearX(pit, row, xx, bound);
917 // try to descend into nested insets
918 Inset * inset = checkInsetHit(cur.bv(), x, y);
919 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
921 // Either we deconst editXY or better we move current_font
922 // and real_current_font to Cursor
927 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
928 //Inset * insetBehind = pars_[pit].getInset(pos);
930 // This should be just before or just behind the
931 // cursor position set above.
932 BOOST_ASSERT((pos != 0 && inset == insetBefore)
933 || inset == pars_[pit].getInset(pos));
935 // Make sure the cursor points to the position before
937 if (inset == insetBefore) {
942 // Try to descend recursively inside the inset.
943 inset = inset->editXY(cur, x, y);
945 if (cur.top().text() == this)
951 bool Text::checkAndActivateInset(Cursor & cur, bool front)
955 if (front && cur.pos() == cur.lastpos())
957 if (!front && cur.pos() == 0)
959 Inset * inset = front ? cur.nextInset() : cur.prevInset();
960 if (!isHighlyEditableInset(inset))
963 * Apparently, when entering an inset we are expected to be positioned
964 * *before* it in the containing paragraph, regardless of the direction
965 * from which we are entering. Otherwise, cursor placement goes awry,
966 * and when we exit from the beginning, we'll be placed *after* the
971 inset->edit(cur, front);
976 bool Text::cursorLeft(Cursor & cur)
978 // Tell BufferView to test for FitCursor in any case!
979 cur.updateFlags(Update::FitCursor);
981 // not at paragraph start?
983 // if on right side of boundary (i.e. not at paragraph end, but line end)
984 // -> skip it, i.e. set boundary to true, i.e. go only logically left
985 // there are some exceptions to ignore this: lineseps, newlines, spaces
987 // some effectless debug code to see the values in the debugger
988 bool bound = cur.boundary();
989 int rowpos = cur.textRow().pos();
991 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
992 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
993 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
995 if (!cur.boundary() &&
996 cur.textRow().pos() == cur.pos() &&
997 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
998 !cur.paragraph().isNewline(cur.pos() - 1) &&
999 !cur.paragraph().isSeparator(cur.pos() - 1)) {
1000 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1003 // go left and try to enter inset
1004 if (checkAndActivateInset(cur, false))
1007 // normal character left
1008 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1011 // move to the previous paragraph or do nothing
1013 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1018 bool Text::cursorRight(Cursor & cur)
1020 // Tell BufferView to test for FitCursor in any case!
1021 cur.updateFlags(Update::FitCursor);
1023 // not at paragraph end?
1024 if (cur.pos() != cur.lastpos()) {
1025 // in front of editable inset, i.e. jump into it?
1026 if (checkAndActivateInset(cur, true))
1029 // if left of boundary -> just jump to right side
1030 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1031 if (cur.boundary() &&
1032 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
1033 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1035 // next position is left of boundary,
1036 // but go to next line for special cases like space, newline, linesep
1038 // some effectless debug code to see the values in the debugger
1039 int endpos = cur.textRow().endpos();
1040 int lastpos = cur.lastpos();
1041 int pos = cur.pos();
1042 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1043 bool newline = cur.paragraph().isNewline(cur.pos());
1044 bool sep = cur.paragraph().isSeparator(cur.pos());
1045 if (cur.pos() != cur.lastpos()) {
1046 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1047 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1048 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1051 if (cur.textRow().endpos() == cur.pos() + 1 &&
1052 cur.textRow().endpos() != cur.lastpos() &&
1053 !cur.paragraph().isNewline(cur.pos()) &&
1054 !cur.paragraph().isLineSeparator(cur.pos()) &&
1055 !cur.paragraph().isSeparator(cur.pos())) {
1056 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1059 // in front of RTL boundary? Stay on this side of the boundary because:
1060 // ab|cDDEEFFghi -> abc|DDEEFFghi
1061 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1062 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1065 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1068 // move to next paragraph
1069 if (cur.pit() != cur.lastpit())
1070 return setCursor(cur, cur.pit() + 1, 0);
1075 bool Text::cursorUpParagraph(Cursor & cur)
1077 bool updated = false;
1079 updated = setCursor(cur, cur.pit(), 0);
1080 else if (cur.pit() != 0)
1081 updated = setCursor(cur, cur.pit() - 1, 0);
1086 bool Text::cursorDownParagraph(Cursor & cur)
1088 bool updated = false;
1089 if (cur.pit() != cur.lastpit())
1090 updated = setCursor(cur, cur.pit() + 1, 0);
1092 updated = setCursor(cur, cur.pit(), cur.lastpos());
1097 // fix the cursor `cur' after a characters has been deleted at `where'
1098 // position. Called by deleteEmptyParagraphMechanism
1099 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1101 // Do nothing if cursor is not in the paragraph where the
1102 // deletion occured,
1103 if (cur.pit() != where.pit())
1106 // If cursor position is after the deletion place update it
1107 if (cur.pos() > where.pos())
1110 // Check also if we don't want to set the cursor on a spot behind the
1111 // pagragraph because we erased the last character.
1112 if (cur.pos() > cur.lastpos())
1113 cur.pos() = cur.lastpos();
1117 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1118 Cursor & old, bool & need_anchor_change)
1120 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1122 Paragraph & oldpar = old.paragraph();
1124 // We allow all kinds of "mumbo-jumbo" when freespacing.
1125 if (oldpar.isFreeSpacing())
1128 /* Ok I'll put some comments here about what is missing.
1129 There are still some small problems that can lead to
1130 double spaces stored in the document file or space at
1131 the beginning of paragraphs(). This happens if you have
1132 the cursor between to spaces and then save. Or if you
1133 cut and paste and the selection have a space at the
1134 beginning and then save right after the paste. (Lgb)
1137 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1138 // delete the LineSeparator.
1141 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1142 // delete the LineSeparator.
1145 bool const same_inset = &old.inset() == &cur.inset();
1146 bool const same_par = same_inset && old.pit() == cur.pit();
1147 bool const same_par_pos = same_par && old.pos() == cur.pos();
1149 // If the chars around the old cursor were spaces, delete one of them.
1150 if (!same_par_pos) {
1151 // Only if the cursor has really moved.
1153 && old.pos() < oldpar.size()
1154 && oldpar.isLineSeparator(old.pos())
1155 && oldpar.isLineSeparator(old.pos() - 1)
1156 && !oldpar.isDeleted(old.pos() - 1)) {
1157 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1158 #ifdef WITH_WARNINGS
1159 #warning This will not work anymore when we have multiple views of the same buffer
1160 // In this case, we will have to correct also the cursors held by
1161 // other bufferviews. It will probably be easier to do that in a more
1162 // automated way in CursorSlice code. (JMarc 26/09/2001)
1164 // correct all cursor parts
1166 fixCursorAfterDelete(cur.top(), old.top());
1167 need_anchor_change = true;
1173 // only do our magic if we changed paragraph
1177 // don't delete anything if this is the ONLY paragraph!
1178 if (old.lastpit() == 0)
1181 // Do not delete empty paragraphs with keepempty set.
1182 if (oldpar.allowEmpty())
1185 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1187 recordUndo(old, Undo::ATOMIC,
1188 max(old.pit() - 1, pit_type(0)),
1189 min(old.pit() + 1, old.lastpit()));
1190 ParagraphList & plist = old.text()->paragraphs();
1191 plist.erase(boost::next(plist.begin(), old.pit()));
1193 // see #warning above
1194 if (cur.depth() >= old.depth()) {
1195 CursorSlice & curslice = cur[old.depth() - 1];
1196 if (&curslice.inset() == &old.inset()
1197 && curslice.pit() > old.pit()) {
1199 // since a paragraph has been deleted, all the
1200 // insets after `old' have been copied and
1201 // their address has changed. Therefore we
1202 // need to `regenerate' cur. (JMarc)
1203 cur.updateInsets(&(cur.bottom().inset()));
1204 need_anchor_change = true;
1210 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1211 need_anchor_change = true;
1212 // We return true here because the Paragraph contents changed and
1213 // we need a redraw before further action is processed.
1221 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1223 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1225 for (pit_type pit = first; pit <= last; ++pit) {
1226 Paragraph & par = pars_[pit];
1228 // We allow all kinds of "mumbo-jumbo" when freespacing.
1229 if (par.isFreeSpacing())
1232 for (pos_type pos = 1; pos < par.size(); ++pos) {
1233 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1234 && !par.isDeleted(pos - 1)) {
1235 if (par.eraseChar(pos - 1, trackChanges)) {
1241 // don't delete anything if this is the only remaining paragraph within the given range
1242 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1246 // don't delete empty paragraphs with keepempty set
1247 if (par.allowEmpty())
1250 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1251 pars_.erase(boost::next(pars_.begin(), pit));
1257 par.stripLeadingSpaces(trackChanges);
1262 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1264 recordUndo(cur, Undo::ATOMIC, first, last);
1268 void Text::recUndo(Cursor & cur, pit_type par) const
1270 recordUndo(cur, Undo::ATOMIC, par, par);