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 docstring 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, docstring 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 " << to_utf8(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 if (align & par.layout()->alignpossible)
654 par.setLabelWidthString(labelwidthstring);
655 params.noindent(noindent);
660 // this really should just insert the inset and not move the cursor.
661 void Text::insertInset(Cursor & cur, Inset * inset)
663 BOOST_ASSERT(this == cur.text());
665 cur.paragraph().insertInset(cur.pos(), inset, current_font,
666 Change(cur.buffer().params().trackChanges ?
667 Change::INSERTED : Change::UNCHANGED));
671 // needed to insert the selection
672 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
674 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
675 current_font, str, autoBreakRows_);
679 // turn double CR to single CR, others are converted into one
680 // blank. Then insertStringAsLines is called
681 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
683 docstring linestr = str;
684 bool newline_inserted = false;
686 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
687 if (linestr[i] == '\n') {
688 if (newline_inserted) {
689 // we know that \r will be ignored by
690 // insertStringAsLines. Of course, it is a dirty
691 // trick, but it works...
692 linestr[i - 1] = '\r';
696 newline_inserted = true;
698 } else if (isPrintable(linestr[i])) {
699 newline_inserted = false;
702 insertStringAsLines(cur, linestr);
706 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
707 bool setfont, bool boundary)
710 setCursorIntern(cur, par, pos, setfont, boundary);
711 return cur.bv().checkDepm(cur, old);
715 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
717 BOOST_ASSERT(par != int(paragraphs().size()));
721 // now some strict checking
722 Paragraph & para = getPar(par);
724 // None of these should happen, but we're scaredy-cats
726 lyxerr << "dont like -1" << endl;
730 if (pos > para.size()) {
731 lyxerr << "dont like 1, pos: " << pos
732 << " size: " << para.size()
733 << " par: " << par << endl;
739 void Text::setCursorIntern(Cursor & cur,
740 pit_type par, pos_type pos, bool setfont, bool boundary)
742 BOOST_ASSERT(this == cur.text());
743 cur.boundary(boundary);
744 setCursor(cur.top(), par, pos);
750 void Text::setCurrentFont(Cursor & cur)
752 BOOST_ASSERT(this == cur.text());
753 pos_type pos = cur.pos();
754 Paragraph & par = cur.paragraph();
756 // are we behind previous char in fact? -> go to that char
757 if (pos > 0 && cur.boundary())
760 // find position to take the font from
762 // paragraph end? -> font of last char
763 if (pos == cur.lastpos())
765 // on space? -> look at the words in front of space
766 else if (pos > 0 && par.isSeparator(pos)) {
767 // abc| def -> font of c
768 // abc |[WERBEH], i.e. boundary==true -> font of c
769 // abc [WERBEH]| def, font of the space
770 if (!isRTLBoundary(cur.buffer(), par, pos))
776 BufferParams const & bufparams = cur.buffer().params();
777 current_font = par.getFontSettings(bufparams, pos);
778 real_current_font = getFont(cur.buffer(), par, pos);
780 // special case for paragraph end
781 if (cur.pos() == cur.lastpos()
782 && isRTLBoundary(cur.buffer(), par, cur.pos())
783 && !cur.boundary()) {
784 Language const * lang = par.getParLanguage(bufparams);
785 current_font.setLanguage(lang);
786 current_font.setNumber(Font::OFF);
787 real_current_font.setLanguage(lang);
788 real_current_font.setNumber(Font::OFF);
792 // y is screen coordinate
793 pit_type Text::getPitNearY(BufferView & bv, int y) const
795 BOOST_ASSERT(!paragraphs().empty());
796 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
797 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
799 << BOOST_CURRENT_FUNCTION
800 << ": y: " << y << " cache size: " << cc.size()
803 // look for highest numbered paragraph with y coordinate less than given y
806 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
807 CoordCache::InnerParPosCache::const_iterator et = cc.end();
808 CoordCache::InnerParPosCache::const_iterator last = et; last--;
810 TextMetrics & tm = bv.textMetrics(this);
811 ParagraphMetrics const & pm = tm.parMetrics(it->first);
813 // If we are off-screen (before the visible part)
815 // and even before the first paragraph in the cache.
816 && y < it->second.y_ - int(pm.ascent())) {
817 // and we are not at the first paragraph in the inset.
820 // then this is the paragraph we are looking for.
822 // rebreak it and update the CoordCache.
823 tm.redoParagraph(pit);
824 bv.coordCache().parPos()[this][pit] =
825 Point(0, it->second.y_ - pm.descent());
829 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
831 // If we are off-screen (after the visible part)
832 if (y > bv.workHeight()
833 // and even after the first paragraph in the cache.
834 && y >= last->second.y_ + int(pm_last.descent())) {
835 pit = last->first + 1;
836 // and we are not at the last paragraph in the inset.
837 if (pit == int(pars_.size()))
839 // then this is the paragraph we are looking for.
840 // rebreak it and update the CoordCache.
841 tm.redoParagraph(pit);
842 bv.coordCache().parPos()[this][pit] =
843 Point(0, last->second.y_ + pm_last.ascent());
847 for (; it != et; ++it) {
849 << BOOST_CURRENT_FUNCTION
850 << " examining: pit: " << it->first
851 << " y: " << it->second.y_
854 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
856 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
863 << BOOST_CURRENT_FUNCTION
864 << ": found best y: " << yy << " for pit: " << pit
871 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
873 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
875 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
876 BOOST_ASSERT(!pm.rows().empty());
877 RowList::const_iterator rit = pm.rows().begin();
878 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
879 for (; rit != rlast; yy += rit->height(), ++rit)
880 if (yy + rit->height() > y)
886 // x,y are absolute screen coordinates
887 // sets cursor recursively descending into nested editable insets
888 Inset * Text::editXY(Cursor & cur, int x, int y)
890 if (lyxerr.debugging(Debug::WORKAREA)) {
891 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
892 cur.bv().coordCache().dump();
894 pit_type pit = getPitNearY(cur.bv(), y);
895 BOOST_ASSERT(pit != -1);
897 Row const & row = getRowNearY(cur.bv(), y, pit);
900 TextMetrics const & tm = cur.bv().textMetrics(this);
901 int xx = x; // is modified by getColumnNearX
902 pos_type const pos = row.pos()
903 + tm.getColumnNearX(pit, row, xx, bound);
909 // try to descend into nested insets
910 Inset * inset = checkInsetHit(cur.bv(), x, y);
911 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
913 // Either we deconst editXY or better we move current_font
914 // and real_current_font to Cursor
919 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
920 //Inset * insetBehind = pars_[pit].getInset(pos);
922 // This should be just before or just behind the
923 // cursor position set above.
924 BOOST_ASSERT((pos != 0 && inset == insetBefore)
925 || inset == pars_[pit].getInset(pos));
927 // Make sure the cursor points to the position before
929 if (inset == insetBefore) {
934 // Try to descend recursively inside the inset.
935 inset = inset->editXY(cur, x, y);
937 if (cur.top().text() == this)
943 bool Text::checkAndActivateInset(Cursor & cur, bool front)
947 if (front && cur.pos() == cur.lastpos())
949 if (!front && cur.pos() == 0)
951 Inset * inset = front ? cur.nextInset() : cur.prevInset();
952 if (!isHighlyEditableInset(inset))
955 * Apparently, when entering an inset we are expected to be positioned
956 * *before* it in the containing paragraph, regardless of the direction
957 * from which we are entering. Otherwise, cursor placement goes awry,
958 * and when we exit from the beginning, we'll be placed *after* the
963 inset->edit(cur, front);
968 bool Text::cursorLeft(Cursor & cur)
970 // Tell BufferView to test for FitCursor in any case!
971 cur.updateFlags(Update::FitCursor);
973 // not at paragraph start?
975 // if on right side of boundary (i.e. not at paragraph end, but line end)
976 // -> skip it, i.e. set boundary to true, i.e. go only logically left
977 // there are some exceptions to ignore this: lineseps, newlines, spaces
979 // some effectless debug code to see the values in the debugger
980 bool bound = cur.boundary();
981 int rowpos = cur.textRow().pos();
983 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
984 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
985 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
987 if (!cur.boundary() &&
988 cur.textRow().pos() == cur.pos() &&
989 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
990 !cur.paragraph().isNewline(cur.pos() - 1) &&
991 !cur.paragraph().isSeparator(cur.pos() - 1)) {
992 return setCursor(cur, cur.pit(), cur.pos(), true, true);
995 // go left and try to enter inset
996 if (checkAndActivateInset(cur, false))
999 // normal character left
1000 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1003 // move to the previous paragraph or do nothing
1005 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1010 bool Text::cursorRight(Cursor & cur)
1012 // Tell BufferView to test for FitCursor in any case!
1013 cur.updateFlags(Update::FitCursor);
1015 // not at paragraph end?
1016 if (cur.pos() != cur.lastpos()) {
1017 // in front of editable inset, i.e. jump into it?
1018 if (checkAndActivateInset(cur, true))
1021 // if left of boundary -> just jump to right side
1022 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1023 if (cur.boundary() &&
1024 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
1025 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1027 // next position is left of boundary,
1028 // but go to next line for special cases like space, newline, linesep
1030 // some effectless debug code to see the values in the debugger
1031 int endpos = cur.textRow().endpos();
1032 int lastpos = cur.lastpos();
1033 int pos = cur.pos();
1034 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1035 bool newline = cur.paragraph().isNewline(cur.pos());
1036 bool sep = cur.paragraph().isSeparator(cur.pos());
1037 if (cur.pos() != cur.lastpos()) {
1038 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1039 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1040 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1043 if (cur.textRow().endpos() == cur.pos() + 1 &&
1044 cur.textRow().endpos() != cur.lastpos() &&
1045 !cur.paragraph().isNewline(cur.pos()) &&
1046 !cur.paragraph().isLineSeparator(cur.pos()) &&
1047 !cur.paragraph().isSeparator(cur.pos())) {
1048 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1051 // in front of RTL boundary? Stay on this side of the boundary because:
1052 // ab|cDDEEFFghi -> abc|DDEEFFghi
1053 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1054 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1057 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1060 // move to next paragraph
1061 if (cur.pit() != cur.lastpit())
1062 return setCursor(cur, cur.pit() + 1, 0);
1067 bool Text::cursorUpParagraph(Cursor & cur)
1069 bool updated = false;
1071 updated = setCursor(cur, cur.pit(), 0);
1072 else if (cur.pit() != 0)
1073 updated = setCursor(cur, cur.pit() - 1, 0);
1078 bool Text::cursorDownParagraph(Cursor & cur)
1080 bool updated = false;
1081 if (cur.pit() != cur.lastpit())
1082 updated = setCursor(cur, cur.pit() + 1, 0);
1084 updated = setCursor(cur, cur.pit(), cur.lastpos());
1089 // fix the cursor `cur' after a characters has been deleted at `where'
1090 // position. Called by deleteEmptyParagraphMechanism
1091 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1093 // Do nothing if cursor is not in the paragraph where the
1094 // deletion occured,
1095 if (cur.pit() != where.pit())
1098 // If cursor position is after the deletion place update it
1099 if (cur.pos() > where.pos())
1102 // Check also if we don't want to set the cursor on a spot behind the
1103 // pagragraph because we erased the last character.
1104 if (cur.pos() > cur.lastpos())
1105 cur.pos() = cur.lastpos();
1109 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1110 Cursor & old, bool & need_anchor_change)
1112 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1114 Paragraph & oldpar = old.paragraph();
1116 // We allow all kinds of "mumbo-jumbo" when freespacing.
1117 if (oldpar.isFreeSpacing())
1120 /* Ok I'll put some comments here about what is missing.
1121 There are still some small problems that can lead to
1122 double spaces stored in the document file or space at
1123 the beginning of paragraphs(). This happens if you have
1124 the cursor between to spaces and then save. Or if you
1125 cut and paste and the selection have a space at the
1126 beginning and then save right after the paste. (Lgb)
1129 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1130 // delete the LineSeparator.
1133 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1134 // delete the LineSeparator.
1137 bool const same_inset = &old.inset() == &cur.inset();
1138 bool const same_par = same_inset && old.pit() == cur.pit();
1139 bool const same_par_pos = same_par && old.pos() == cur.pos();
1141 // If the chars around the old cursor were spaces, delete one of them.
1142 if (!same_par_pos) {
1143 // Only if the cursor has really moved.
1145 && old.pos() < oldpar.size()
1146 && oldpar.isLineSeparator(old.pos())
1147 && oldpar.isLineSeparator(old.pos() - 1)
1148 && !oldpar.isDeleted(old.pos() - 1)) {
1149 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1150 #ifdef WITH_WARNINGS
1151 #warning This will not work anymore when we have multiple views of the same buffer
1152 // In this case, we will have to correct also the cursors held by
1153 // other bufferviews. It will probably be easier to do that in a more
1154 // automated way in CursorSlice code. (JMarc 26/09/2001)
1156 // correct all cursor parts
1158 fixCursorAfterDelete(cur.top(), old.top());
1159 need_anchor_change = true;
1165 // only do our magic if we changed paragraph
1169 // don't delete anything if this is the ONLY paragraph!
1170 if (old.lastpit() == 0)
1173 // Do not delete empty paragraphs with keepempty set.
1174 if (oldpar.allowEmpty())
1177 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1179 recordUndo(old, Undo::ATOMIC,
1180 max(old.pit() - 1, pit_type(0)),
1181 min(old.pit() + 1, old.lastpit()));
1182 ParagraphList & plist = old.text()->paragraphs();
1183 plist.erase(boost::next(plist.begin(), old.pit()));
1185 // see #warning above
1186 if (cur.depth() >= old.depth()) {
1187 CursorSlice & curslice = cur[old.depth() - 1];
1188 if (&curslice.inset() == &old.inset()
1189 && curslice.pit() > old.pit()) {
1191 // since a paragraph has been deleted, all the
1192 // insets after `old' have been copied and
1193 // their address has changed. Therefore we
1194 // need to `regenerate' cur. (JMarc)
1195 cur.updateInsets(&(cur.bottom().inset()));
1196 need_anchor_change = true;
1202 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1203 need_anchor_change = true;
1204 // We return true here because the Paragraph contents changed and
1205 // we need a redraw before further action is processed.
1213 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1215 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1217 for (pit_type pit = first; pit <= last; ++pit) {
1218 Paragraph & par = pars_[pit];
1220 // We allow all kinds of "mumbo-jumbo" when freespacing.
1221 if (par.isFreeSpacing())
1224 for (pos_type pos = 1; pos < par.size(); ++pos) {
1225 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1226 && !par.isDeleted(pos - 1)) {
1227 if (par.eraseChar(pos - 1, trackChanges)) {
1233 // don't delete anything if this is the only remaining paragraph within the given range
1234 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1238 // don't delete empty paragraphs with keepempty set
1239 if (par.allowEmpty())
1242 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1243 pars_.erase(boost::next(pars_.begin(), pit));
1249 par.stripLeadingSpaces(trackChanges);
1254 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1256 recordUndo(cur, Undo::ATOMIC, first, last);
1260 void Text::recUndo(Cursor & cur, pit_type par) const
1262 recordUndo(cur, Undo::ATOMIC, par, par);