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();
154 BufferParams const & params = buffer.params();
155 pos_type const body_pos = par.beginOfBody();
157 // We specialize the 95% common case:
158 if (!par.getDepth()) {
159 Font f = par.getFontSettings(params, pos);
160 if (!isMainText(buffer))
161 applyOuterFont(buffer, f);
164 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
165 lf = layout->labelfont;
166 rlf = layout->reslabelfont;
169 rlf = layout->resfont;
171 // In case the default family has been customized
172 if (lf.family() == Font::INHERIT_FAMILY)
173 rlf.setFamily(params.getFont().family());
174 return f.realize(rlf);
177 // The uncommon case need not be optimized as much
180 layoutfont = layout->labelfont;
182 layoutfont = layout->font;
184 Font font = par.getFontSettings(params, pos);
185 font.realize(layoutfont);
187 if (!isMainText(buffer))
188 applyOuterFont(buffer, font);
190 // Find the pit value belonging to paragraph. This will not break
191 // even if pars_ would not be a vector anymore.
192 // Performance appears acceptable.
194 pit_type pit = pars_.size();
195 for (pit_type it = 0; it < pit; ++it)
196 if (&pars_[it] == &par) {
200 // Realize against environment font information
201 // NOTE: the cast to pit_type should be removed when pit_type
202 // changes to a unsigned integer.
203 if (pit < pit_type(pars_.size()))
204 font.realize(outerFont(pit, pars_));
206 // Realize with the fonts of lesser depth.
207 font.realize(params.getFont());
212 // There are currently two font mechanisms in LyX:
213 // 1. The font attributes in a lyxtext, and
214 // 2. The inset-specific font properties, defined in an inset's
215 // metrics() and draw() methods and handed down the inset chain through
216 // the pi/mi parameters, and stored locally in a lyxtext in font_.
217 // This is where the two are integrated in the final fully realized
219 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
221 lf.reduce(buffer.params().getFont());
223 lf.setLanguage(font.language());
228 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
230 Layout_ptr const & layout = pars_[pit].layout();
232 if (!pars_[pit].getDepth()) {
233 Font lf = layout->resfont;
234 // In case the default family has been customized
235 if (layout->font.family() == Font::INHERIT_FAMILY)
236 lf.setFamily(buffer.params().getFont().family());
240 Font font = layout->font;
241 // Realize with the fonts of lesser depth.
242 //font.realize(outerFont(pit, paragraphs()));
243 font.realize(buffer.params().getFont());
249 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
251 Layout_ptr const & layout = par.layout();
253 if (!par.getDepth()) {
254 Font lf = layout->reslabelfont;
255 // In case the default family has been customized
256 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
257 lf.setFamily(buffer.params().getFont().family());
261 Font font = layout->labelfont;
262 // Realize with the fonts of lesser depth.
263 font.realize(buffer.params().getFont());
269 void Text::setCharFont(Buffer const & buffer, pit_type pit,
270 pos_type pos, Font const & fnt)
273 Layout_ptr const & layout = pars_[pit].layout();
275 // Get concrete layout font to reduce against
278 if (pos < pars_[pit].beginOfBody())
279 layoutfont = layout->labelfont;
281 layoutfont = layout->font;
283 // Realize against environment font information
284 if (pars_[pit].getDepth()) {
286 while (!layoutfont.resolved() &&
287 tp != pit_type(paragraphs().size()) &&
288 pars_[tp].getDepth()) {
289 tp = outerHook(tp, paragraphs());
290 if (tp != pit_type(paragraphs().size()))
291 layoutfont.realize(pars_[tp].layout()->font);
295 // Inside inset, apply the inset's font attributes if any
297 if (!isMainText(buffer))
298 layoutfont.realize(font_);
300 layoutfont.realize(buffer.params().getFont());
302 // Now, reduce font against full layout font
303 font.reduce(layoutfont);
305 pars_[pit].setFont(pos, font);
309 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
310 pos_type pos, Font const & font, bool toggleall)
312 BOOST_ASSERT(pars_[pit].isInset(pos) &&
313 pars_[pit].getInset(pos)->noFontChange());
315 Inset * const inset = pars_[pit].getInset(pos);
316 DocIterator dit = doc_iterator_begin(*inset);
317 // start of the last cell
318 DocIterator end = dit;
319 end.idx() = end.lastidx();
322 Text * text = dit.text();
323 Inset * cell = dit.realInset();
325 DocIterator cellbegin = doc_iterator_begin(*cell);
326 // last position of the cell
327 DocIterator cellend = cellbegin;
328 cellend.pit() = cellend.lastpit();
329 cellend.pos() = cellend.lastpos();
330 text->setFont(buffer, cellbegin, cellend, font, toggleall);
339 // return past-the-last paragraph influenced by a layout change on pit
340 pit_type Text::undoSpan(pit_type pit)
342 pit_type end = paragraphs().size();
343 pit_type nextpit = pit + 1;
346 //because of parindents
347 if (!pars_[pit].getDepth())
348 return boost::next(nextpit);
349 //because of depth constrains
350 for (; nextpit != end; ++pit, ++nextpit) {
351 if (!pars_[pit].getDepth())
358 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
359 docstring const & layout)
361 BOOST_ASSERT(start != end);
363 BufferParams const & bufparams = buffer.params();
364 Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
366 for (pit_type pit = start; pit != end; ++pit) {
367 Paragraph & par = pars_[pit];
368 par.applyLayout(lyxlayout);
369 if (lyxlayout->margintype == MARGIN_MANUAL)
370 par.setLabelWidthString(par.translateIfPossible(
371 lyxlayout->labelstring(), buffer.params()));
376 // set layout over selection and make a total rebreak of those paragraphs
377 void Text::setLayout(Cursor & cur, docstring const & layout)
379 BOOST_ASSERT(this == cur.text());
380 // special handling of new environment insets
381 BufferView & bv = cur.bv();
382 BufferParams const & params = bv.buffer()->params();
383 Layout_ptr const & lyxlayout = params.getTextClass()[layout];
384 if (lyxlayout->is_environment) {
385 // move everything in a new environment inset
386 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
387 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
388 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
389 lyx::dispatch(FuncRequest(LFUN_CUT));
390 Inset * inset = new InsetEnvironment(params, layout);
391 insertInset(cur, inset);
392 //inset->edit(cur, true);
393 //lyx::dispatch(FuncRequest(LFUN_PASTE));
397 pit_type start = cur.selBegin().pit();
398 pit_type end = cur.selEnd().pit() + 1;
399 pit_type undopit = undoSpan(end - 1);
400 recUndo(cur, start, undopit - 1);
401 setLayout(cur.buffer(), start, end, layout);
402 updateLabels(cur.buffer());
406 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
407 Paragraph const & par, int max_depth)
409 if (par.layout()->labeltype == LABEL_BIBLIO)
411 int const depth = par.params().depth();
412 if (type == Text::INC_DEPTH && depth < max_depth)
414 if (type == Text::DEC_DEPTH && depth > 0)
420 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
422 BOOST_ASSERT(this == cur.text());
423 // this happens when selecting several cells in tabular (bug 2630)
424 if (cur.selBegin().idx() != cur.selEnd().idx())
427 pit_type const beg = cur.selBegin().pit();
428 pit_type const end = cur.selEnd().pit() + 1;
429 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
431 for (pit_type pit = beg; pit != end; ++pit) {
432 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
434 max_depth = pars_[pit].getMaxDepthAfter();
440 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
442 BOOST_ASSERT(this == cur.text());
443 pit_type const beg = cur.selBegin().pit();
444 pit_type const end = cur.selEnd().pit() + 1;
445 recordUndoSelection(cur);
446 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
448 for (pit_type pit = beg; pit != end; ++pit) {
449 Paragraph & par = pars_[pit];
450 if (lyx::changeDepthAllowed(type, par, max_depth)) {
451 int const depth = par.params().depth();
452 if (type == INC_DEPTH)
453 par.params().depth(depth + 1);
455 par.params().depth(depth - 1);
457 max_depth = par.getMaxDepthAfter();
459 // this handles the counter labels, and also fixes up
460 // depth values for follow-on (child) paragraphs
461 updateLabels(cur.buffer());
465 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
467 BOOST_ASSERT(this == cur.text());
468 // Set the current_font
469 // Determine basis font
471 pit_type pit = cur.pit();
472 if (cur.pos() < pars_[pit].beginOfBody())
473 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
475 layoutfont = getLayoutFont(cur.buffer(), pit);
477 // Update current font
478 real_current_font.update(font,
479 cur.buffer().params().language,
482 // Reduce to implicit settings
483 current_font = real_current_font;
484 current_font.reduce(layoutfont);
485 // And resolve it completely
486 real_current_font.realize(layoutfont);
488 // if there is no selection that's all we need to do
489 if (!cur.selection())
492 // Ok, we have a selection.
493 recordUndoSelection(cur);
495 setFont(cur.buffer(), cur.selectionBegin(), cur.selectionEnd(), font,
500 void Text::setFont(Buffer const & buffer, DocIterator const & begin,
501 DocIterator const & end, Font const & font,
504 // Don't use forwardChar here as ditend might have
505 // pos() == lastpos() and forwardChar would miss it.
506 // Can't use forwardPos either as this descends into
508 Language const * language = buffer.params().language;
509 for (DocIterator dit = begin; dit != end; dit.forwardPosNoDescend()) {
510 if (dit.pos() != dit.lastpos()) {
511 pit_type const pit = dit.pit();
512 pos_type const pos = dit.pos();
513 if (pars_[pit].isInset(pos) &&
514 pars_[pit].getInset(pos)->noFontChange())
515 // We need to propagate the font change to all
516 // text cells of the inset (bug 1973).
517 // FIXME: This should change, see documentation
518 // of noFontChange in Inset.h
519 setInsetFont(buffer, pit, pos, font, toggleall);
520 Font f = getFont(buffer, dit.paragraph(), pos);
521 f.update(font, language, toggleall);
522 setCharFont(buffer, pit, pos, f);
528 // the cursor set functions have a special mechanism. When they
529 // realize you left an empty paragraph, they will delete it.
531 bool Text::cursorHome(Cursor & cur)
533 BOOST_ASSERT(this == cur.text());
534 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
535 Row const & row = pm.getRow(cur.pos(),cur.boundary());
536 return setCursor(cur, cur.pit(), row.pos());
540 bool Text::cursorEnd(Cursor & cur)
542 BOOST_ASSERT(this == cur.text());
543 // if not on the last row of the par, put the cursor before
544 // the final space exept if I have a spanning inset or one string
545 // is so long that we force a break.
546 pos_type end = cur.textRow().endpos();
548 // empty text, end-1 is no valid position
550 bool boundary = false;
551 if (end != cur.lastpos()) {
552 if (!cur.paragraph().isLineSeparator(end-1)
553 && !cur.paragraph().isNewline(end-1))
558 return setCursor(cur, cur.pit(), end, true, boundary);
562 bool Text::cursorTop(Cursor & cur)
564 BOOST_ASSERT(this == cur.text());
565 return setCursor(cur, 0, 0);
569 bool Text::cursorBottom(Cursor & cur)
571 BOOST_ASSERT(this == cur.text());
572 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
576 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
578 BOOST_ASSERT(this == cur.text());
579 // If the mask is completely neutral, tell user
580 if (font == Font(Font::ALL_IGNORE)) {
581 // Could only happen with user style
582 cur.message(_("No font change defined."));
586 // Try implicit word selection
587 // If there is a change in the language the implicit word selection
589 CursorSlice resetCursor = cur.top();
590 bool implicitSelection =
591 font.language() == ignore_language
592 && font.number() == Font::IGNORE
593 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
596 setFont(cur, font, toggleall);
598 // Implicit selections are cleared afterwards
599 // and cursor is set to the original position.
600 if (implicitSelection) {
601 cur.clearSelection();
602 cur.top() = resetCursor;
608 docstring Text::getStringToIndex(Cursor const & cur)
610 BOOST_ASSERT(this == cur.text());
614 idxstring = cur.selectionAsString(false);
616 // Try implicit word selection. If there is a change
617 // in the language the implicit word selection is
620 selectWord(tmpcur, PREVIOUS_WORD);
622 if (!tmpcur.selection())
623 cur.message(_("Nothing to index!"));
624 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
625 cur.message(_("Cannot index more than one paragraph!"));
627 idxstring = tmpcur.selectionAsString(false);
634 void Text::setParagraph(Cursor & cur,
635 Spacing const & spacing, LyXAlignment align,
636 docstring const & labelwidthstring, bool noindent)
638 BOOST_ASSERT(cur.text());
639 // make sure that the depth behind the selection are restored, too
640 pit_type undopit = undoSpan(cur.selEnd().pit());
641 recUndo(cur, cur.selBegin().pit(), undopit - 1);
643 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
645 Paragraph & par = pars_[pit];
646 ParagraphParameters & params = par.params();
647 params.spacing(spacing);
649 // does the layout allow the new alignment?
650 if (align & par.layout()->alignpossible)
652 par.setLabelWidthString(labelwidthstring);
653 params.noindent(noindent);
658 // this really should just insert the inset and not move the cursor.
659 void Text::insertInset(Cursor & cur, Inset * inset)
661 BOOST_ASSERT(this == cur.text());
663 cur.paragraph().insertInset(cur.pos(), inset, current_font,
664 Change(cur.buffer().params().trackChanges ?
665 Change::INSERTED : Change::UNCHANGED));
669 // needed to insert the selection
670 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
672 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
673 current_font, str, autoBreakRows_);
677 // turn double CR to single CR, others are converted into one
678 // blank. Then insertStringAsLines is called
679 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
681 docstring linestr = str;
682 bool newline_inserted = false;
684 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
685 if (linestr[i] == '\n') {
686 if (newline_inserted) {
687 // we know that \r will be ignored by
688 // insertStringAsLines. Of course, it is a dirty
689 // trick, but it works...
690 linestr[i - 1] = '\r';
694 newline_inserted = true;
696 } else if (isPrintable(linestr[i])) {
697 newline_inserted = false;
700 insertStringAsLines(cur, linestr);
704 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
705 bool setfont, bool boundary)
708 setCursorIntern(cur, par, pos, setfont, boundary);
709 return cur.bv().checkDepm(cur, old);
713 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
715 BOOST_ASSERT(par != int(paragraphs().size()));
719 // now some strict checking
720 Paragraph & para = getPar(par);
722 // None of these should happen, but we're scaredy-cats
724 lyxerr << "dont like -1" << endl;
728 if (pos > para.size()) {
729 lyxerr << "dont like 1, pos: " << pos
730 << " size: " << para.size()
731 << " par: " << par << endl;
737 void Text::setCursorIntern(Cursor & cur,
738 pit_type par, pos_type pos, bool setfont, bool boundary)
740 BOOST_ASSERT(this == cur.text());
741 cur.boundary(boundary);
742 setCursor(cur.top(), par, pos);
748 void Text::setCurrentFont(Cursor & cur)
750 BOOST_ASSERT(this == cur.text());
751 pos_type pos = cur.pos();
752 Paragraph & par = cur.paragraph();
754 // are we behind previous char in fact? -> go to that char
755 if (pos > 0 && cur.boundary())
758 // find position to take the font from
760 // paragraph end? -> font of last char
761 if (pos == cur.lastpos())
763 // on space? -> look at the words in front of space
764 else if (pos > 0 && par.isSeparator(pos)) {
765 // abc| def -> font of c
766 // abc |[WERBEH], i.e. boundary==true -> font of c
767 // abc [WERBEH]| def, font of the space
768 if (!isRTLBoundary(cur.buffer(), par, pos))
774 BufferParams const & bufparams = cur.buffer().params();
775 current_font = par.getFontSettings(bufparams, pos);
776 real_current_font = getFont(cur.buffer(), par, pos);
778 // special case for paragraph end
779 if (cur.pos() == cur.lastpos()
780 && isRTLBoundary(cur.buffer(), par, cur.pos())
781 && !cur.boundary()) {
782 Language const * lang = par.getParLanguage(bufparams);
783 current_font.setLanguage(lang);
784 current_font.setNumber(Font::OFF);
785 real_current_font.setLanguage(lang);
786 real_current_font.setNumber(Font::OFF);
790 // y is screen coordinate
791 pit_type Text::getPitNearY(BufferView & bv, int y) const
793 BOOST_ASSERT(!paragraphs().empty());
794 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
795 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
797 << BOOST_CURRENT_FUNCTION
798 << ": y: " << y << " cache size: " << cc.size()
801 // look for highest numbered paragraph with y coordinate less than given y
804 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
805 CoordCache::InnerParPosCache::const_iterator et = cc.end();
806 CoordCache::InnerParPosCache::const_iterator last = et; last--;
808 TextMetrics & tm = bv.textMetrics(this);
809 ParagraphMetrics const & pm = tm.parMetrics(it->first);
811 // If we are off-screen (before the visible part)
813 // and even before the first paragraph in the cache.
814 && y < it->second.y_ - int(pm.ascent())) {
815 // and we are not at the first paragraph in the inset.
818 // then this is the paragraph we are looking for.
820 // rebreak it and update the CoordCache.
821 tm.redoParagraph(pit);
822 bv.coordCache().parPos()[this][pit] =
823 Point(0, it->second.y_ - pm.descent());
827 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
829 // If we are off-screen (after the visible part)
830 if (y > bv.workHeight()
831 // and even after the first paragraph in the cache.
832 && y >= last->second.y_ + int(pm_last.descent())) {
833 pit = last->first + 1;
834 // and we are not at the last paragraph in the inset.
835 if (pit == int(pars_.size()))
837 // then this is the paragraph we are looking for.
838 // rebreak it and update the CoordCache.
839 tm.redoParagraph(pit);
840 bv.coordCache().parPos()[this][pit] =
841 Point(0, last->second.y_ + pm_last.ascent());
845 for (; it != et; ++it) {
847 << BOOST_CURRENT_FUNCTION
848 << " examining: pit: " << it->first
849 << " y: " << it->second.y_
852 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
854 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
861 << BOOST_CURRENT_FUNCTION
862 << ": found best y: " << yy << " for pit: " << pit
869 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
871 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
873 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
874 BOOST_ASSERT(!pm.rows().empty());
875 RowList::const_iterator rit = pm.rows().begin();
876 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
877 for (; rit != rlast; yy += rit->height(), ++rit)
878 if (yy + rit->height() > y)
884 // x,y are absolute screen coordinates
885 // sets cursor recursively descending into nested editable insets
886 Inset * Text::editXY(Cursor & cur, int x, int y)
888 if (lyxerr.debugging(Debug::WORKAREA)) {
889 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
890 cur.bv().coordCache().dump();
892 pit_type pit = getPitNearY(cur.bv(), y);
893 BOOST_ASSERT(pit != -1);
895 Row const & row = getRowNearY(cur.bv(), y, pit);
898 TextMetrics const & tm = cur.bv().textMetrics(this);
899 int xx = x; // is modified by getColumnNearX
900 pos_type const pos = row.pos()
901 + tm.getColumnNearX(pit, row, xx, bound);
907 // try to descend into nested insets
908 Inset * inset = checkInsetHit(cur.bv(), x, y);
909 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
911 // Either we deconst editXY or better we move current_font
912 // and real_current_font to Cursor
917 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
918 //Inset * insetBehind = pars_[pit].getInset(pos);
920 // This should be just before or just behind the
921 // cursor position set above.
922 BOOST_ASSERT((pos != 0 && inset == insetBefore)
923 || inset == pars_[pit].getInset(pos));
925 // Make sure the cursor points to the position before
927 if (inset == insetBefore) {
932 // Try to descend recursively inside the inset.
933 inset = inset->editXY(cur, x, y);
935 if (cur.top().text() == this)
941 bool Text::checkAndActivateInset(Cursor & cur, bool front)
945 if (front && cur.pos() == cur.lastpos())
947 if (!front && cur.pos() == 0)
949 Inset * inset = front ? cur.nextInset() : cur.prevInset();
950 if (!isHighlyEditableInset(inset))
953 * Apparently, when entering an inset we are expected to be positioned
954 * *before* it in the containing paragraph, regardless of the direction
955 * from which we are entering. Otherwise, cursor placement goes awry,
956 * and when we exit from the beginning, we'll be placed *after* the
961 inset->edit(cur, front);
966 bool Text::cursorLeft(Cursor & cur)
968 // Tell BufferView to test for FitCursor in any case!
969 cur.updateFlags(Update::FitCursor);
971 // not at paragraph start?
973 // if on right side of boundary (i.e. not at paragraph end, but line end)
974 // -> skip it, i.e. set boundary to true, i.e. go only logically left
975 // there are some exceptions to ignore this: lineseps, newlines, spaces
977 // some effectless debug code to see the values in the debugger
978 bool bound = cur.boundary();
979 int rowpos = cur.textRow().pos();
981 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
982 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
983 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
985 if (!cur.boundary() &&
986 cur.textRow().pos() == cur.pos() &&
987 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
988 !cur.paragraph().isNewline(cur.pos() - 1) &&
989 !cur.paragraph().isSeparator(cur.pos() - 1)) {
990 return setCursor(cur, cur.pit(), cur.pos(), true, true);
993 // go left and try to enter inset
994 if (checkAndActivateInset(cur, false))
997 // normal character left
998 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1001 // move to the previous paragraph or do nothing
1003 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1008 bool Text::cursorRight(Cursor & cur)
1010 // Tell BufferView to test for FitCursor in any case!
1011 cur.updateFlags(Update::FitCursor);
1013 // not at paragraph end?
1014 if (cur.pos() != cur.lastpos()) {
1015 // in front of editable inset, i.e. jump into it?
1016 if (checkAndActivateInset(cur, true))
1019 // if left of boundary -> just jump to right side
1020 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1021 if (cur.boundary() &&
1022 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
1023 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1025 // next position is left of boundary,
1026 // but go to next line for special cases like space, newline, linesep
1028 // some effectless debug code to see the values in the debugger
1029 int endpos = cur.textRow().endpos();
1030 int lastpos = cur.lastpos();
1031 int pos = cur.pos();
1032 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1033 bool newline = cur.paragraph().isNewline(cur.pos());
1034 bool sep = cur.paragraph().isSeparator(cur.pos());
1035 if (cur.pos() != cur.lastpos()) {
1036 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1037 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1038 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1041 if (cur.textRow().endpos() == cur.pos() + 1 &&
1042 cur.textRow().endpos() != cur.lastpos() &&
1043 !cur.paragraph().isNewline(cur.pos()) &&
1044 !cur.paragraph().isLineSeparator(cur.pos()) &&
1045 !cur.paragraph().isSeparator(cur.pos())) {
1046 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1049 // in front of RTL boundary? Stay on this side of the boundary because:
1050 // ab|cDDEEFFghi -> abc|DDEEFFghi
1051 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1052 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1055 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1058 // move to next paragraph
1059 if (cur.pit() != cur.lastpit())
1060 return setCursor(cur, cur.pit() + 1, 0);
1065 bool Text::cursorUpParagraph(Cursor & cur)
1067 bool updated = false;
1069 updated = setCursor(cur, cur.pit(), 0);
1070 else if (cur.pit() != 0)
1071 updated = setCursor(cur, cur.pit() - 1, 0);
1076 bool Text::cursorDownParagraph(Cursor & cur)
1078 bool updated = false;
1079 if (cur.pit() != cur.lastpit())
1080 updated = setCursor(cur, cur.pit() + 1, 0);
1082 updated = setCursor(cur, cur.pit(), cur.lastpos());
1087 // fix the cursor `cur' after a characters has been deleted at `where'
1088 // position. Called by deleteEmptyParagraphMechanism
1089 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1091 // Do nothing if cursor is not in the paragraph where the
1092 // deletion occured,
1093 if (cur.pit() != where.pit())
1096 // If cursor position is after the deletion place update it
1097 if (cur.pos() > where.pos())
1100 // Check also if we don't want to set the cursor on a spot behind the
1101 // pagragraph because we erased the last character.
1102 if (cur.pos() > cur.lastpos())
1103 cur.pos() = cur.lastpos();
1107 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1108 Cursor & old, bool & need_anchor_change)
1110 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1112 Paragraph & oldpar = old.paragraph();
1114 // We allow all kinds of "mumbo-jumbo" when freespacing.
1115 if (oldpar.isFreeSpacing())
1118 /* Ok I'll put some comments here about what is missing.
1119 There are still some small problems that can lead to
1120 double spaces stored in the document file or space at
1121 the beginning of paragraphs(). This happens if you have
1122 the cursor between to spaces and then save. Or if you
1123 cut and paste and the selection have a space at the
1124 beginning and then save right after the paste. (Lgb)
1127 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1128 // delete the LineSeparator.
1131 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1132 // delete the LineSeparator.
1135 bool const same_inset = &old.inset() == &cur.inset();
1136 bool const same_par = same_inset && old.pit() == cur.pit();
1137 bool const same_par_pos = same_par && old.pos() == cur.pos();
1139 // If the chars around the old cursor were spaces, delete one of them.
1140 if (!same_par_pos) {
1141 // Only if the cursor has really moved.
1143 && old.pos() < oldpar.size()
1144 && oldpar.isLineSeparator(old.pos())
1145 && oldpar.isLineSeparator(old.pos() - 1)
1146 && !oldpar.isDeleted(old.pos() - 1)) {
1147 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1148 // FIXME: This will not work anymore when we have multiple views of the same buffer
1149 // In this case, we will have to correct also the cursors held by
1150 // other bufferviews. It will probably be easier to do that in a more
1151 // automated way in CursorSlice code. (JMarc 26/09/2001)
1152 // correct all cursor parts
1154 fixCursorAfterDelete(cur.top(), old.top());
1155 need_anchor_change = true;
1161 // only do our magic if we changed paragraph
1165 // don't delete anything if this is the ONLY paragraph!
1166 if (old.lastpit() == 0)
1169 // Do not delete empty paragraphs with keepempty set.
1170 if (oldpar.allowEmpty())
1173 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1175 recordUndo(old, Undo::ATOMIC,
1176 max(old.pit() - 1, pit_type(0)),
1177 min(old.pit() + 1, old.lastpit()));
1178 ParagraphList & plist = old.text()->paragraphs();
1179 plist.erase(boost::next(plist.begin(), old.pit()));
1181 // see #warning (FIXME?) above
1182 if (cur.depth() >= old.depth()) {
1183 CursorSlice & curslice = cur[old.depth() - 1];
1184 if (&curslice.inset() == &old.inset()
1185 && curslice.pit() > old.pit()) {
1187 // since a paragraph has been deleted, all the
1188 // insets after `old' have been copied and
1189 // their address has changed. Therefore we
1190 // need to `regenerate' cur. (JMarc)
1191 cur.updateInsets(&(cur.bottom().inset()));
1192 need_anchor_change = true;
1198 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1199 need_anchor_change = true;
1200 // We return true here because the Paragraph contents changed and
1201 // we need a redraw before further action is processed.
1209 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1211 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1213 for (pit_type pit = first; pit <= last; ++pit) {
1214 Paragraph & par = pars_[pit];
1216 // We allow all kinds of "mumbo-jumbo" when freespacing.
1217 if (par.isFreeSpacing())
1220 for (pos_type pos = 1; pos < par.size(); ++pos) {
1221 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1222 && !par.isDeleted(pos - 1)) {
1223 if (par.eraseChar(pos - 1, trackChanges)) {
1229 // don't delete anything if this is the only remaining paragraph within the given range
1230 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1234 // don't delete empty paragraphs with keepempty set
1235 if (par.allowEmpty())
1238 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1239 pars_.erase(boost::next(pars_.begin(), pit));
1245 par.stripLeadingSpaces(trackChanges);
1250 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1252 recordUndo(cur, Undo::ATOMIC, first, last);
1256 void Text::recUndo(Cursor & cur, pit_type par) const
1258 recordUndo(cur, Undo::ATOMIC, par, par);