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"
33 #include "CoordCache.h"
35 #include "CutAndPaste.h"
37 #include "DispatchResult.h"
38 #include "ErrorList.h"
39 #include "FuncRequest.h"
46 #include "Paragraph.h"
47 #include "TextMetrics.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "ParIterator.h"
52 #include "ServerSocket.h"
56 #include "frontends/FontMetrics.h"
58 #include "insets/InsetEnvironment.h"
60 #include "mathed/InsetMathHull.h"
62 #include "support/textutils.h"
64 #include <boost/current_function.hpp>
72 using std::ostringstream;
76 using std::istringstream;
79 : current_font(Font::ALL_INHERIT),
80 background_color_(Color::background),
85 bool Text::isMainText(Buffer const & buffer) const
87 return &buffer.text() == this;
91 //takes screen x,y coordinates
92 Inset * Text::checkInsetHit(BufferView & bv, int x, int y)
94 pit_type pit = getPitNearY(bv, y);
95 BOOST_ASSERT(pit != -1);
97 Paragraph const & par = pars_[pit];
100 << BOOST_CURRENT_FUNCTION
105 InsetList::const_iterator iit = par.insetlist.begin();
106 InsetList::const_iterator iend = par.insetlist.end();
107 for (; iit != iend; ++iit) {
108 Inset * inset = iit->inset;
111 << BOOST_CURRENT_FUNCTION
112 << ": examining inset " << inset << endl;
114 if (bv.coordCache().getInsets().has(inset))
116 << BOOST_CURRENT_FUNCTION
117 << ": xo: " << inset->xo(bv) << "..."
118 << inset->xo(bv) + inset->width()
119 << " yo: " << inset->yo(bv) - inset->ascent()
121 << inset->yo(bv) + inset->descent()
125 << BOOST_CURRENT_FUNCTION
126 << ": inset has no cached position" << endl;
128 if (inset->covers(bv, x, y)) {
130 << BOOST_CURRENT_FUNCTION
131 << ": Hit inset: " << inset << endl;
136 << BOOST_CURRENT_FUNCTION
137 << ": No inset hit. " << endl;
143 // Gets the fully instantiated font at a given position in a paragraph
144 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
145 // The difference is that this one is used for displaying, and thus we
146 // are allowed to make cosmetic improvements. For instance make footnotes
148 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
149 pos_type const pos) const
151 BOOST_ASSERT(pos >= 0);
153 Layout_ptr const & layout = par.layout();
155 BufferParams const & params = buffer.params();
156 pos_type const body_pos = par.beginOfBody();
158 // We specialize the 95% common case:
159 if (!par.getDepth()) {
160 Font f = par.getFontSettings(params, pos);
161 if (!isMainText(buffer))
162 applyOuterFont(buffer, f);
165 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
166 lf = layout->labelfont;
167 rlf = layout->reslabelfont;
170 rlf = layout->resfont;
172 // In case the default family has been customized
173 if (lf.family() == Font::INHERIT_FAMILY)
174 rlf.setFamily(params.getFont().family());
175 return f.realize(rlf);
178 // The uncommon case need not be optimized as much
181 layoutfont = layout->labelfont;
183 layoutfont = layout->font;
185 Font font = par.getFontSettings(params, pos);
186 font.realize(layoutfont);
188 if (!isMainText(buffer))
189 applyOuterFont(buffer, font);
191 // Find the pit value belonging to paragraph. This will not break
192 // even if pars_ would not be a vector anymore.
193 // Performance appears acceptable.
195 pit_type pit = pars_.size();
196 for (pit_type it = 0; it < pit; ++it)
197 if (&pars_[it] == &par) {
201 // Realize against environment font information
202 // NOTE: the cast to pit_type should be removed when pit_type
203 // changes to a unsigned integer.
204 if (pit < pit_type(pars_.size()))
205 font.realize(outerFont(pit, pars_));
207 // Realize with the fonts of lesser depth.
208 font.realize(params.getFont());
213 // There are currently two font mechanisms in LyX:
214 // 1. The font attributes in a lyxtext, and
215 // 2. The inset-specific font properties, defined in an inset's
216 // metrics() and draw() methods and handed down the inset chain through
217 // the pi/mi parameters, and stored locally in a lyxtext in font_.
218 // This is where the two are integrated in the final fully realized
220 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
222 lf.reduce(buffer.params().getFont());
224 lf.setLanguage(font.language());
229 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
231 Layout_ptr const & layout = pars_[pit].layout();
233 if (!pars_[pit].getDepth()) {
234 Font lf = layout->resfont;
235 // In case the default family has been customized
236 if (layout->font.family() == Font::INHERIT_FAMILY)
237 lf.setFamily(buffer.params().getFont().family());
241 Font font = layout->font;
242 // Realize with the fonts of lesser depth.
243 //font.realize(outerFont(pit, paragraphs()));
244 font.realize(buffer.params().getFont());
250 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
252 Layout_ptr const & layout = par.layout();
254 if (!par.getDepth()) {
255 Font lf = layout->reslabelfont;
256 // In case the default family has been customized
257 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
258 lf.setFamily(buffer.params().getFont().family());
262 Font font = layout->labelfont;
263 // Realize with the fonts of lesser depth.
264 font.realize(buffer.params().getFont());
270 void Text::setCharFont(Buffer const & buffer, pit_type pit,
271 pos_type pos, Font const & fnt)
274 Layout_ptr const & layout = pars_[pit].layout();
276 // Get concrete layout font to reduce against
279 if (pos < pars_[pit].beginOfBody())
280 layoutfont = layout->labelfont;
282 layoutfont = layout->font;
284 // Realize against environment font information
285 if (pars_[pit].getDepth()) {
287 while (!layoutfont.resolved() &&
288 tp != pit_type(paragraphs().size()) &&
289 pars_[tp].getDepth()) {
290 tp = outerHook(tp, paragraphs());
291 if (tp != pit_type(paragraphs().size()))
292 layoutfont.realize(pars_[tp].layout()->font);
296 // Inside inset, apply the inset's font attributes if any
298 if (!isMainText(buffer))
299 layoutfont.realize(font_);
301 layoutfont.realize(buffer.params().getFont());
303 // Now, reduce font against full layout font
304 font.reduce(layoutfont);
306 pars_[pit].setFont(pos, font);
310 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
311 pos_type pos, Font const & font, bool toggleall)
313 BOOST_ASSERT(pars_[pit].isInset(pos) &&
314 pars_[pit].getInset(pos)->noFontChange());
316 Inset * const inset = pars_[pit].getInset(pos);
317 DocIterator dit = doc_iterator_begin(*inset);
318 // start of the last cell
319 DocIterator end = dit;
320 end.idx() = end.lastidx();
323 Text * text = dit.text();
324 Inset * cell = dit.realInset();
326 DocIterator cellbegin = doc_iterator_begin(*cell);
327 // last position of the cell
328 DocIterator cellend = cellbegin;
329 cellend.pit() = cellend.lastpit();
330 cellend.pos() = cellend.lastpos();
331 text->setFont(buffer, cellbegin.top(), cellend.top(), font, toggleall);
340 // return past-the-last paragraph influenced by a layout change on pit
341 pit_type Text::undoSpan(pit_type pit)
343 pit_type end = paragraphs().size();
344 pit_type nextpit = pit + 1;
347 //because of parindents
348 if (!pars_[pit].getDepth())
349 return boost::next(nextpit);
350 //because of depth constrains
351 for (; nextpit != end; ++pit, ++nextpit) {
352 if (!pars_[pit].getDepth())
359 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
360 docstring const & layout)
362 BOOST_ASSERT(start != end);
364 BufferParams const & bufparams = buffer.params();
365 Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
367 for (pit_type pit = start; pit != end; ++pit) {
368 Paragraph & par = pars_[pit];
369 par.applyLayout(lyxlayout);
370 if (lyxlayout->margintype == MARGIN_MANUAL)
371 par.setLabelWidthString(par.translateIfPossible(
372 lyxlayout->labelstring(), buffer.params()));
377 // set layout over selection and make a total rebreak of those paragraphs
378 void Text::setLayout(Cursor & cur, docstring const & layout)
380 BOOST_ASSERT(this == cur.text());
381 // special handling of new environment insets
382 BufferView & bv = cur.bv();
383 BufferParams const & params = bv.buffer()->params();
384 Layout_ptr const & lyxlayout = params.getTextClass()[layout];
385 if (lyxlayout->is_environment) {
386 // move everything in a new environment inset
387 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
388 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
389 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
390 lyx::dispatch(FuncRequest(LFUN_CUT));
391 Inset * inset = new InsetEnvironment(params, layout);
392 insertInset(cur, inset);
393 //inset->edit(cur, true);
394 //lyx::dispatch(FuncRequest(LFUN_PASTE));
398 pit_type start = cur.selBegin().pit();
399 pit_type end = cur.selEnd().pit() + 1;
400 pit_type undopit = undoSpan(end - 1);
401 recUndo(cur, start, undopit - 1);
402 setLayout(cur.buffer(), start, end, layout);
403 updateLabels(cur.buffer());
407 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
408 Paragraph const & par, int max_depth)
410 if (par.layout()->labeltype == LABEL_BIBLIO)
412 int const depth = par.params().depth();
413 if (type == Text::INC_DEPTH && depth < max_depth)
415 if (type == Text::DEC_DEPTH && depth > 0)
421 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
423 BOOST_ASSERT(this == cur.text());
424 // this happens when selecting several cells in tabular (bug 2630)
425 if (cur.selBegin().idx() != cur.selEnd().idx())
428 pit_type const beg = cur.selBegin().pit();
429 pit_type const end = cur.selEnd().pit() + 1;
430 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
432 for (pit_type pit = beg; pit != end; ++pit) {
433 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
435 max_depth = pars_[pit].getMaxDepthAfter();
441 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
443 BOOST_ASSERT(this == cur.text());
444 pit_type const beg = cur.selBegin().pit();
445 pit_type const end = cur.selEnd().pit() + 1;
446 recordUndoSelection(cur);
447 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
449 for (pit_type pit = beg; pit != end; ++pit) {
450 Paragraph & par = pars_[pit];
451 if (lyx::changeDepthAllowed(type, par, max_depth)) {
452 int const depth = par.params().depth();
453 if (type == INC_DEPTH)
454 par.params().depth(depth + 1);
456 par.params().depth(depth - 1);
458 max_depth = par.getMaxDepthAfter();
460 // this handles the counter labels, and also fixes up
461 // depth values for follow-on (child) paragraphs
462 updateLabels(cur.buffer());
466 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
468 BOOST_ASSERT(this == cur.text());
469 // Set the current_font
470 // Determine basis font
472 pit_type pit = cur.pit();
473 if (cur.pos() < pars_[pit].beginOfBody())
474 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
476 layoutfont = getLayoutFont(cur.buffer(), pit);
478 // Update current font
479 real_current_font.update(font,
480 cur.buffer().params().language,
483 // Reduce to implicit settings
484 current_font = real_current_font;
485 current_font.reduce(layoutfont);
486 // And resolve it completely
487 real_current_font.realize(layoutfont);
489 // if there is no selection that's all we need to do
490 if (!cur.selection())
493 // Ok, we have a selection.
494 recordUndoSelection(cur);
496 setFont(cur.buffer(), cur.selectionBegin().top(),
497 cur.selectionEnd().top(), font, toggleall);
501 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
502 CursorSlice const & end, Font const & font,
505 // Don't use forwardChar here as ditend might have
506 // pos() == lastpos() and forwardChar would miss it.
507 // Can't use forwardPos either as this descends into
509 Language const * language = buffer.params().language;
510 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
511 if (dit.pos() != dit.lastpos()) {
512 pit_type const pit = dit.pit();
513 pos_type const pos = dit.pos();
514 if (pars_[pit].isInset(pos) &&
515 pars_[pit].getInset(pos)->noFontChange())
516 // We need to propagate the font change to all
517 // text cells of the inset (bug 1973).
518 // FIXME: This should change, see documentation
519 // of noFontChange in Inset.h
520 setInsetFont(buffer, pit, pos, font, toggleall);
521 Font f = getFont(buffer, dit.paragraph(), pos);
522 f.update(font, language, toggleall);
523 setCharFont(buffer, pit, pos, f);
529 // the cursor set functions have a special mechanism. When they
530 // realize you left an empty paragraph, they will delete it.
532 bool Text::cursorHome(Cursor & cur)
534 BOOST_ASSERT(this == cur.text());
535 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
536 Row const & row = pm.getRow(cur.pos(),cur.boundary());
537 return setCursor(cur, cur.pit(), row.pos());
541 bool Text::cursorEnd(Cursor & cur)
543 BOOST_ASSERT(this == cur.text());
544 // if not on the last row of the par, put the cursor before
545 // the final space exept if I have a spanning inset or one string
546 // is so long that we force a break.
547 pos_type end = cur.textRow().endpos();
549 // empty text, end-1 is no valid position
551 bool boundary = false;
552 if (end != cur.lastpos()) {
553 if (!cur.paragraph().isLineSeparator(end-1)
554 && !cur.paragraph().isNewline(end-1))
559 return setCursor(cur, cur.pit(), end, true, boundary);
563 bool Text::cursorTop(Cursor & cur)
565 BOOST_ASSERT(this == cur.text());
566 return setCursor(cur, 0, 0);
570 bool Text::cursorBottom(Cursor & cur)
572 BOOST_ASSERT(this == cur.text());
573 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
577 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
579 BOOST_ASSERT(this == cur.text());
580 // If the mask is completely neutral, tell user
581 if (font == Font(Font::ALL_IGNORE)) {
582 // Could only happen with user style
583 cur.message(_("No font change defined."));
587 // Try implicit word selection
588 // If there is a change in the language the implicit word selection
590 CursorSlice resetCursor = cur.top();
591 bool implicitSelection =
592 font.language() == ignore_language
593 && font.number() == Font::IGNORE
594 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
597 setFont(cur, font, toggleall);
599 // Implicit selections are cleared afterwards
600 // and cursor is set to the original position.
601 if (implicitSelection) {
602 cur.clearSelection();
603 cur.top() = resetCursor;
609 docstring Text::getStringToIndex(Cursor const & cur)
611 BOOST_ASSERT(this == cur.text());
615 idxstring = cur.selectionAsString(false);
617 // Try implicit word selection. If there is a change
618 // in the language the implicit word selection is
621 selectWord(tmpcur, PREVIOUS_WORD);
623 if (!tmpcur.selection())
624 cur.message(_("Nothing to index!"));
625 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
626 cur.message(_("Cannot index more than one paragraph!"));
628 idxstring = tmpcur.selectionAsString(false);
635 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
637 BOOST_ASSERT(cur.text());
638 // make sure that the depth behind the selection are restored, too
639 pit_type undopit = undoSpan(cur.selEnd().pit());
640 recUndo(cur, cur.selBegin().pit(), undopit - 1);
642 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
644 Paragraph & par = pars_[pit];
645 ParagraphParameters params = par.params();
646 params.read(to_utf8(arg), merge);
647 Layout const & layout = *(par.layout());
648 par.params().apply(params, layout);
653 //FIXME This is a little redundant now, but it's probably worth keeping,
654 //especially if we're going to go away from using serialization internally
656 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
658 BOOST_ASSERT(cur.text());
659 // make sure that the depth behind the selection are restored, too
660 pit_type undopit = undoSpan(cur.selEnd().pit());
661 recUndo(cur, cur.selBegin().pit(), undopit - 1);
663 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
665 Paragraph & par = pars_[pit];
666 Layout const & layout = *(par.layout());
667 par.params().apply(p, layout);
672 // this really should just insert the inset and not move the cursor.
673 void Text::insertInset(Cursor & cur, Inset * inset)
675 BOOST_ASSERT(this == cur.text());
677 cur.paragraph().insertInset(cur.pos(), inset, current_font,
678 Change(cur.buffer().params().trackChanges ?
679 Change::INSERTED : Change::UNCHANGED));
683 // needed to insert the selection
684 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
686 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
687 current_font, str, autoBreakRows_);
691 // turn double CR to single CR, others are converted into one
692 // blank. Then insertStringAsLines is called
693 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
695 docstring linestr = str;
696 bool newline_inserted = false;
698 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
699 if (linestr[i] == '\n') {
700 if (newline_inserted) {
701 // we know that \r will be ignored by
702 // insertStringAsLines. Of course, it is a dirty
703 // trick, but it works...
704 linestr[i - 1] = '\r';
708 newline_inserted = true;
710 } else if (isPrintable(linestr[i])) {
711 newline_inserted = false;
714 insertStringAsLines(cur, linestr);
718 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
719 bool setfont, bool boundary)
722 setCursorIntern(cur, par, pos, setfont, boundary);
723 return cur.bv().checkDepm(cur, old);
727 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
729 BOOST_ASSERT(par != int(paragraphs().size()));
733 // now some strict checking
734 Paragraph & para = getPar(par);
736 // None of these should happen, but we're scaredy-cats
738 lyxerr << "dont like -1" << endl;
742 if (pos > para.size()) {
743 lyxerr << "dont like 1, pos: " << pos
744 << " size: " << para.size()
745 << " par: " << par << endl;
751 void Text::setCursorIntern(Cursor & cur,
752 pit_type par, pos_type pos, bool setfont, bool boundary)
754 BOOST_ASSERT(this == cur.text());
755 cur.boundary(boundary);
756 setCursor(cur.top(), par, pos);
762 void Text::setCurrentFont(Cursor & cur)
764 BOOST_ASSERT(this == cur.text());
765 pos_type pos = cur.pos();
766 Paragraph & par = cur.paragraph();
768 // are we behind previous char in fact? -> go to that char
769 if (pos > 0 && cur.boundary())
772 // find position to take the font from
774 // paragraph end? -> font of last char
775 if (pos == cur.lastpos())
777 // on space? -> look at the words in front of space
778 else if (pos > 0 && par.isSeparator(pos)) {
779 // abc| def -> font of c
780 // abc |[WERBEH], i.e. boundary==true -> font of c
781 // abc [WERBEH]| def, font of the space
782 if (!isRTLBoundary(cur.buffer(), par, pos))
788 BufferParams const & bufparams = cur.buffer().params();
789 current_font = par.getFontSettings(bufparams, pos);
790 real_current_font = getFont(cur.buffer(), par, pos);
792 // special case for paragraph end
793 if (cur.pos() == cur.lastpos()
794 && isRTLBoundary(cur.buffer(), par, cur.pos())
795 && !cur.boundary()) {
796 Language const * lang = par.getParLanguage(bufparams);
797 current_font.setLanguage(lang);
798 current_font.setNumber(Font::OFF);
799 real_current_font.setLanguage(lang);
800 real_current_font.setNumber(Font::OFF);
804 // y is screen coordinate
805 pit_type Text::getPitNearY(BufferView & bv, int y) const
807 BOOST_ASSERT(!paragraphs().empty());
808 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
809 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
811 << BOOST_CURRENT_FUNCTION
812 << ": y: " << y << " cache size: " << cc.size()
815 // look for highest numbered paragraph with y coordinate less than given y
818 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
819 CoordCache::InnerParPosCache::const_iterator et = cc.end();
820 CoordCache::InnerParPosCache::const_iterator last = et; last--;
822 TextMetrics & tm = bv.textMetrics(this);
823 ParagraphMetrics const & pm = tm.parMetrics(it->first);
825 // If we are off-screen (before the visible part)
827 // and even before the first paragraph in the cache.
828 && y < it->second.y_ - int(pm.ascent())) {
829 // and we are not at the first paragraph in the inset.
832 // then this is the paragraph we are looking for.
834 // rebreak it and update the CoordCache.
835 tm.redoParagraph(pit);
836 bv.coordCache().parPos()[this][pit] =
837 Point(0, it->second.y_ - pm.descent());
841 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
843 // If we are off-screen (after the visible part)
844 if (y > bv.workHeight()
845 // and even after the first paragraph in the cache.
846 && y >= last->second.y_ + int(pm_last.descent())) {
847 pit = last->first + 1;
848 // and we are not at the last paragraph in the inset.
849 if (pit == int(pars_.size()))
851 // then this is the paragraph we are looking for.
852 // rebreak it and update the CoordCache.
853 tm.redoParagraph(pit);
854 bv.coordCache().parPos()[this][pit] =
855 Point(0, last->second.y_ + pm_last.ascent());
859 for (; it != et; ++it) {
861 << BOOST_CURRENT_FUNCTION
862 << " examining: pit: " << it->first
863 << " y: " << it->second.y_
866 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
868 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
875 << BOOST_CURRENT_FUNCTION
876 << ": found best y: " << yy << " for pit: " << pit
883 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
885 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
887 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
888 BOOST_ASSERT(!pm.rows().empty());
889 RowList::const_iterator rit = pm.rows().begin();
890 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
891 for (; rit != rlast; yy += rit->height(), ++rit)
892 if (yy + rit->height() > y)
898 // x,y are absolute screen coordinates
899 // sets cursor recursively descending into nested editable insets
900 Inset * Text::editXY(Cursor & cur, int x, int y)
902 if (lyxerr.debugging(Debug::WORKAREA)) {
903 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
904 cur.bv().coordCache().dump();
906 pit_type pit = getPitNearY(cur.bv(), y);
907 BOOST_ASSERT(pit != -1);
909 Row const & row = getRowNearY(cur.bv(), y, pit);
912 TextMetrics const & tm = cur.bv().textMetrics(this);
913 int xx = x; // is modified by getColumnNearX
914 pos_type const pos = row.pos()
915 + tm.getColumnNearX(pit, row, xx, bound);
921 // try to descend into nested insets
922 Inset * inset = checkInsetHit(cur.bv(), x, y);
923 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
925 // Either we deconst editXY or better we move current_font
926 // and real_current_font to Cursor
931 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
932 //Inset * insetBehind = pars_[pit].getInset(pos);
934 // This should be just before or just behind the
935 // cursor position set above.
936 BOOST_ASSERT((pos != 0 && inset == insetBefore)
937 || inset == pars_[pit].getInset(pos));
939 // Make sure the cursor points to the position before
941 if (inset == insetBefore) {
946 // Try to descend recursively inside the inset.
947 inset = inset->editXY(cur, x, y);
949 if (cur.top().text() == this)
955 bool Text::checkAndActivateInset(Cursor & cur, bool front)
959 if (front && cur.pos() == cur.lastpos())
961 if (!front && cur.pos() == 0)
963 Inset * inset = front ? cur.nextInset() : cur.prevInset();
964 if (!isHighlyEditableInset(inset))
967 * Apparently, when entering an inset we are expected to be positioned
968 * *before* it in the containing paragraph, regardless of the direction
969 * from which we are entering. Otherwise, cursor placement goes awry,
970 * and when we exit from the beginning, we'll be placed *after* the
975 inset->edit(cur, front);
980 bool Text::cursorLeft(Cursor & cur)
982 // Tell BufferView to test for FitCursor in any case!
983 cur.updateFlags(Update::FitCursor);
985 // not at paragraph start?
987 // if on right side of boundary (i.e. not at paragraph end, but line end)
988 // -> skip it, i.e. set boundary to true, i.e. go only logically left
989 // there are some exceptions to ignore this: lineseps, newlines, spaces
991 // some effectless debug code to see the values in the debugger
992 bool bound = cur.boundary();
993 int rowpos = cur.textRow().pos();
995 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
996 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
997 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
999 if (!cur.boundary() &&
1000 cur.textRow().pos() == cur.pos() &&
1001 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
1002 !cur.paragraph().isNewline(cur.pos() - 1) &&
1003 !cur.paragraph().isSeparator(cur.pos() - 1)) {
1004 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1007 // go left and try to enter inset
1008 if (checkAndActivateInset(cur, false))
1011 // normal character left
1012 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1015 // move to the previous paragraph or do nothing
1017 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1022 bool Text::cursorRight(Cursor & cur)
1024 // Tell BufferView to test for FitCursor in any case!
1025 cur.updateFlags(Update::FitCursor);
1027 // not at paragraph end?
1028 if (cur.pos() != cur.lastpos()) {
1029 // in front of editable inset, i.e. jump into it?
1030 if (checkAndActivateInset(cur, true))
1033 // if left of boundary -> just jump to right side
1034 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1035 if (cur.boundary() &&
1036 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
1037 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1039 // next position is left of boundary,
1040 // but go to next line for special cases like space, newline, linesep
1042 // some effectless debug code to see the values in the debugger
1043 int endpos = cur.textRow().endpos();
1044 int lastpos = cur.lastpos();
1045 int pos = cur.pos();
1046 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1047 bool newline = cur.paragraph().isNewline(cur.pos());
1048 bool sep = cur.paragraph().isSeparator(cur.pos());
1049 if (cur.pos() != cur.lastpos()) {
1050 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1051 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1052 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1055 if (cur.textRow().endpos() == cur.pos() + 1 &&
1056 cur.textRow().endpos() != cur.lastpos() &&
1057 !cur.paragraph().isNewline(cur.pos()) &&
1058 !cur.paragraph().isLineSeparator(cur.pos()) &&
1059 !cur.paragraph().isSeparator(cur.pos())) {
1060 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1063 // in front of RTL boundary? Stay on this side of the boundary because:
1064 // ab|cDDEEFFghi -> abc|DDEEFFghi
1065 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1066 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1069 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1072 // move to next paragraph
1073 if (cur.pit() != cur.lastpit())
1074 return setCursor(cur, cur.pit() + 1, 0);
1079 bool Text::cursorUpParagraph(Cursor & cur)
1081 bool updated = false;
1083 updated = setCursor(cur, cur.pit(), 0);
1084 else if (cur.pit() != 0)
1085 updated = setCursor(cur, cur.pit() - 1, 0);
1090 bool Text::cursorDownParagraph(Cursor & cur)
1092 bool updated = false;
1093 if (cur.pit() != cur.lastpit())
1094 updated = setCursor(cur, cur.pit() + 1, 0);
1096 updated = setCursor(cur, cur.pit(), cur.lastpos());
1101 // fix the cursor `cur' after a characters has been deleted at `where'
1102 // position. Called by deleteEmptyParagraphMechanism
1103 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1105 // Do nothing if cursor is not in the paragraph where the
1106 // deletion occured,
1107 if (cur.pit() != where.pit())
1110 // If cursor position is after the deletion place update it
1111 if (cur.pos() > where.pos())
1114 // Check also if we don't want to set the cursor on a spot behind the
1115 // pagragraph because we erased the last character.
1116 if (cur.pos() > cur.lastpos())
1117 cur.pos() = cur.lastpos();
1121 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1122 Cursor & old, bool & need_anchor_change)
1124 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1126 Paragraph & oldpar = old.paragraph();
1128 // We allow all kinds of "mumbo-jumbo" when freespacing.
1129 if (oldpar.isFreeSpacing())
1132 /* Ok I'll put some comments here about what is missing.
1133 There are still some small problems that can lead to
1134 double spaces stored in the document file or space at
1135 the beginning of paragraphs(). This happens if you have
1136 the cursor between to spaces and then save. Or if you
1137 cut and paste and the selection have a space at the
1138 beginning and then save right after the paste. (Lgb)
1141 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1142 // delete the LineSeparator.
1145 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1146 // delete the LineSeparator.
1149 bool const same_inset = &old.inset() == &cur.inset();
1150 bool const same_par = same_inset && old.pit() == cur.pit();
1151 bool const same_par_pos = same_par && old.pos() == cur.pos();
1153 // If the chars around the old cursor were spaces, delete one of them.
1154 if (!same_par_pos) {
1155 // Only if the cursor has really moved.
1157 && old.pos() < oldpar.size()
1158 && oldpar.isLineSeparator(old.pos())
1159 && oldpar.isLineSeparator(old.pos() - 1)
1160 && !oldpar.isDeleted(old.pos() - 1)) {
1161 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1162 // FIXME: This will not work anymore when we have multiple views of the same buffer
1163 // In this case, we will have to correct also the cursors held by
1164 // other bufferviews. It will probably be easier to do that in a more
1165 // automated way in CursorSlice code. (JMarc 26/09/2001)
1166 // correct all cursor parts
1168 fixCursorAfterDelete(cur.top(), old.top());
1169 need_anchor_change = true;
1175 // only do our magic if we changed paragraph
1179 // don't delete anything if this is the ONLY paragraph!
1180 if (old.lastpit() == 0)
1183 // Do not delete empty paragraphs with keepempty set.
1184 if (oldpar.allowEmpty())
1187 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1189 recordUndo(old, Undo::ATOMIC,
1190 max(old.pit() - 1, pit_type(0)),
1191 min(old.pit() + 1, old.lastpit()));
1192 ParagraphList & plist = old.text()->paragraphs();
1193 plist.erase(boost::next(plist.begin(), old.pit()));
1195 // see #warning (FIXME?) above
1196 if (cur.depth() >= old.depth()) {
1197 CursorSlice & curslice = cur[old.depth() - 1];
1198 if (&curslice.inset() == &old.inset()
1199 && curslice.pit() > old.pit()) {
1201 // since a paragraph has been deleted, all the
1202 // insets after `old' have been copied and
1203 // their address has changed. Therefore we
1204 // need to `regenerate' cur. (JMarc)
1205 cur.updateInsets(&(cur.bottom().inset()));
1206 need_anchor_change = true;
1212 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1213 need_anchor_change = true;
1214 // We return true here because the Paragraph contents changed and
1215 // we need a redraw before further action is processed.
1223 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1225 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1227 for (pit_type pit = first; pit <= last; ++pit) {
1228 Paragraph & par = pars_[pit];
1230 // We allow all kinds of "mumbo-jumbo" when freespacing.
1231 if (par.isFreeSpacing())
1234 for (pos_type pos = 1; pos < par.size(); ++pos) {
1235 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1236 && !par.isDeleted(pos - 1)) {
1237 if (par.eraseChar(pos - 1, trackChanges)) {
1243 // don't delete anything if this is the only remaining paragraph within the given range
1244 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1248 // don't delete empty paragraphs with keepempty set
1249 if (par.allowEmpty())
1252 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1253 pars_.erase(boost::next(pars_.begin(), pit));
1259 par.stripLeadingSpaces(trackChanges);
1264 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1266 recordUndo(cur, Undo::ATOMIC, first, last);
1270 void Text::recUndo(Cursor & cur, pit_type par) const
1272 recordUndo(cur, Undo::ATOMIC, par, par);