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
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "BufferList.h"
27 #include "BufferParams.h"
28 #include "BufferView.h"
29 #include "bufferview_funcs.h"
31 #include "CoordCache.h"
33 #include "CutAndPaste.h"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
44 #include "Paragraph.h"
45 #include "TextMetrics.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "ParIterator.h"
50 #include "ServerSocket.h"
54 #include "frontends/FontMetrics.h"
56 #include "insets/InsetEnvironment.h"
58 #include "mathed/InsetMathHull.h"
60 #include "support/textutils.h"
62 #include <boost/current_function.hpp>
70 using std::ostringstream;
77 : current_font(Font::ALL_INHERIT),
78 background_color_(Color::background),
83 bool Text::isMainText(Buffer const & buffer) const
85 return &buffer.text() == this;
89 //takes screen x,y coordinates
90 Inset * Text::checkInsetHit(BufferView & bv, int x, int y)
92 pit_type pit = getPitNearY(bv, y);
93 BOOST_ASSERT(pit != -1);
95 Paragraph const & par = pars_[pit];
98 << BOOST_CURRENT_FUNCTION
103 InsetList::const_iterator iit = par.insetlist.begin();
104 InsetList::const_iterator iend = par.insetlist.end();
105 for (; iit != iend; ++iit) {
106 Inset * inset = iit->inset;
109 << BOOST_CURRENT_FUNCTION
110 << ": examining inset " << inset << endl;
112 if (bv.coordCache().getInsets().has(inset))
114 << BOOST_CURRENT_FUNCTION
115 << ": xo: " << inset->xo(bv) << "..."
116 << inset->xo(bv) + inset->width()
117 << " yo: " << inset->yo(bv) - inset->ascent()
119 << inset->yo(bv) + inset->descent()
123 << BOOST_CURRENT_FUNCTION
124 << ": inset has no cached position" << endl;
126 if (inset->covers(bv, x, y)) {
128 << BOOST_CURRENT_FUNCTION
129 << ": Hit inset: " << inset << endl;
134 << BOOST_CURRENT_FUNCTION
135 << ": No inset hit. " << endl;
141 // Gets the fully instantiated font at a given position in a paragraph
142 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
143 // The difference is that this one is used for displaying, and thus we
144 // are allowed to make cosmetic improvements. For instance make footnotes
146 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
147 pos_type const pos) const
149 BOOST_ASSERT(pos >= 0);
151 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, cellend, 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 string 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, string 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 " << 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(), cur.selectionEnd(), font,
501 void Text::setFont(Buffer const & buffer, DocIterator const & begin,
502 DocIterator 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 (DocIterator dit = begin; dit != end; dit.forwardPosNoDescend()) {
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::setParagraph(Cursor & cur,
636 Spacing const & spacing, LyXAlignment align,
637 docstring const & labelwidthstring, bool noindent)
639 BOOST_ASSERT(cur.text());
640 // make sure that the depth behind the selection are restored, too
641 pit_type undopit = undoSpan(cur.selEnd().pit());
642 recUndo(cur, cur.selBegin().pit(), undopit - 1);
644 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
646 Paragraph & par = pars_[pit];
647 ParagraphParameters & params = par.params();
648 params.spacing(spacing);
650 // does the layout allow the new alignment?
651 Layout_ptr const & layout = par.layout();
653 if (align == LYX_ALIGN_LAYOUT)
654 align = layout->align;
655 if (align & layout->alignpossible) {
656 if (align == layout->align)
657 params.align(LYX_ALIGN_LAYOUT);
661 par.setLabelWidthString(labelwidthstring);
662 params.noindent(noindent);
667 // this really should just insert the inset and not move the cursor.
668 void Text::insertInset(Cursor & cur, Inset * inset)
670 BOOST_ASSERT(this == cur.text());
672 cur.paragraph().insertInset(cur.pos(), inset,
673 Change(cur.buffer().params().trackChanges ?
674 Change::INSERTED : Change::UNCHANGED));
678 // needed to insert the selection
679 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
681 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
682 current_font, str, autoBreakRows_);
686 // turn double CR to single CR, others are converted into one
687 // blank. Then insertStringAsLines is called
688 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
690 docstring linestr = str;
691 bool newline_inserted = false;
693 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
694 if (linestr[i] == '\n') {
695 if (newline_inserted) {
696 // we know that \r will be ignored by
697 // insertStringAsLines. Of course, it is a dirty
698 // trick, but it works...
699 linestr[i - 1] = '\r';
703 newline_inserted = true;
705 } else if (isPrintable(linestr[i])) {
706 newline_inserted = false;
709 insertStringAsLines(cur, linestr);
713 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
714 bool setfont, bool boundary)
717 setCursorIntern(cur, par, pos, setfont, boundary);
718 return cur.bv().checkDepm(cur, old);
722 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
724 BOOST_ASSERT(par != int(paragraphs().size()));
728 // now some strict checking
729 Paragraph & para = getPar(par);
731 // None of these should happen, but we're scaredy-cats
733 lyxerr << "dont like -1" << endl;
737 if (pos > para.size()) {
738 lyxerr << "dont like 1, pos: " << pos
739 << " size: " << para.size()
740 << " par: " << par << endl;
746 void Text::setCursorIntern(Cursor & cur,
747 pit_type par, pos_type pos, bool setfont, bool boundary)
749 BOOST_ASSERT(this == cur.text());
750 cur.boundary(boundary);
751 setCursor(cur.top(), par, pos);
757 void Text::setCurrentFont(Cursor & cur)
759 BOOST_ASSERT(this == cur.text());
760 pos_type pos = cur.pos();
761 Paragraph & par = cur.paragraph();
763 if (cur.boundary() && pos > 0 && pos < cur.lastpos()) {
765 // We may have just moved to the previous row ---
766 // we're going to be needing its bidi tables!
767 bidi.computeTables(par, cur.buffer(), cur.textRow());
771 if (pos == cur.lastpos())
773 else // potentional bug... BUG (Lgb)
774 if (par.isSeparator(pos)) {
775 if (pos > cur.textRow().pos() &&
776 bidi.level(pos) % 2 ==
777 bidi.level(pos - 1) % 2)
779 else if (pos + 1 < cur.lastpos())
784 BufferParams const & bufparams = cur.buffer().params();
785 current_font = par.getFontSettings(bufparams, pos);
786 real_current_font = getFont(cur.buffer(), par, pos);
788 if (cur.pos() == cur.lastpos()
789 && bidi.isBoundary(cur.buffer(), par, cur.pos())
790 && !cur.boundary()) {
791 Language const * lang = par.getParLanguage(bufparams);
792 current_font.setLanguage(lang);
793 current_font.setNumber(Font::OFF);
794 real_current_font.setLanguage(lang);
795 real_current_font.setNumber(Font::OFF);
799 // y is screen coordinate
800 pit_type Text::getPitNearY(BufferView & bv, int y) const
802 BOOST_ASSERT(!paragraphs().empty());
803 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
804 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
806 << BOOST_CURRENT_FUNCTION
807 << ": y: " << y << " cache size: " << cc.size()
810 // look for highest numbered paragraph with y coordinate less than given y
813 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
814 CoordCache::InnerParPosCache::const_iterator et = cc.end();
815 CoordCache::InnerParPosCache::const_iterator last = et; last--;
817 TextMetrics & tm = bv.textMetrics(this);
818 ParagraphMetrics const & pm = tm.parMetrics(it->first);
820 // If we are off-screen (before the visible part)
822 // and even before the first paragraph in the cache.
823 && y < it->second.y_ - int(pm.ascent())) {
824 // and we are not at the first paragraph in the inset.
827 // then this is the paragraph we are looking for.
829 // rebreak it and update the CoordCache.
830 tm.redoParagraph(pit);
831 bv.coordCache().parPos()[this][pit] =
832 Point(0, it->second.y_ - pm.descent());
836 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
838 // If we are off-screen (after the visible part)
839 if (y > bv.workHeight()
840 // and even after the first paragraph in the cache.
841 && y >= last->second.y_ + int(pm_last.descent())) {
842 pit = last->first + 1;
843 // and we are not at the last paragraph in the inset.
844 if (pit == int(pars_.size()))
846 // then this is the paragraph we are looking for.
847 // rebreak it and update the CoordCache.
848 tm.redoParagraph(pit);
849 bv.coordCache().parPos()[this][pit] =
850 Point(0, last->second.y_ + pm_last.ascent());
854 for (; it != et; ++it) {
856 << BOOST_CURRENT_FUNCTION
857 << " examining: pit: " << it->first
858 << " y: " << it->second.y_
861 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
863 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
870 << BOOST_CURRENT_FUNCTION
871 << ": found best y: " << yy << " for pit: " << pit
878 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
880 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
882 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
883 BOOST_ASSERT(!pm.rows().empty());
884 RowList::const_iterator rit = pm.rows().begin();
885 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
886 for (; rit != rlast; yy += rit->height(), ++rit)
887 if (yy + rit->height() > y)
893 // x,y are absolute screen coordinates
894 // sets cursor recursively descending into nested editable insets
895 Inset * Text::editXY(Cursor & cur, int x, int y)
897 if (lyxerr.debugging(Debug::WORKAREA)) {
898 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
899 cur.bv().coordCache().dump();
901 pit_type pit = getPitNearY(cur.bv(), y);
902 BOOST_ASSERT(pit != -1);
904 Row const & row = getRowNearY(cur.bv(), y, pit);
907 TextMetrics const & tm = cur.bv().textMetrics(this);
908 int xx = x; // is modified by getColumnNearX
909 pos_type const pos = row.pos()
910 + tm.getColumnNearX(pit, row, xx, bound);
916 // try to descend into nested insets
917 Inset * inset = checkInsetHit(cur.bv(), x, y);
918 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
920 // Either we deconst editXY or better we move current_font
921 // and real_current_font to Cursor
926 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
927 //Inset * insetBehind = pars_[pit].getInset(pos);
929 // This should be just before or just behind the
930 // cursor position set above.
931 BOOST_ASSERT((pos != 0 && inset == insetBefore)
932 || inset == pars_[pit].getInset(pos));
934 // Make sure the cursor points to the position before
936 if (inset == insetBefore)
939 // Try to descend recursively inside the inset.
940 inset = inset->editXY(cur, x, y);
942 if (cur.top().text() == this)
948 bool Text::checkAndActivateInset(Cursor & cur, bool front)
952 if (front && cur.pos() == cur.lastpos())
954 if (!front && cur.pos() == 0)
956 Inset * inset = front ? cur.nextInset() : cur.prevInset();
957 if (!isHighlyEditableInset(inset))
960 * Apparently, when entering an inset we are expected to be positioned
961 * *before* it in the containing paragraph, regardless of the direction
962 * from which we are entering. Otherwise, cursor placement goes awry,
963 * and when we exit from the beginning, we'll be placed *after* the
968 inset->edit(cur, front);
973 bool Text::cursorLeft(Cursor & cur)
975 // Tell BufferView to test for FitCursor in any case!
976 cur.updateFlags(Update::FitCursor);
980 return setCursor(cur, cur.pit(), cur.pos(), true, false);
982 bool updateNeeded = false;
983 // If checkAndActivateInset returns true, that means that
984 // the cursor was placed inside it, so we're done
985 if (!checkAndActivateInset(cur, false)) {
986 if (!cur.boundary() &&
987 cur.textRow().pos() == cur.pos()
988 // FIXME: the following two conditions are copied
989 // from cursorRight; however, isLineSeparator()
990 // is definitely wrong here, isNewline I'm not sure
991 // about. I'm leaving them as comments for now,
992 // until we understand why they should or shouldn't
995 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
996 !cur.paragraph().isNewline(cur.pos() - 1)*/) {
997 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(),
1000 updateNeeded |= setCursor(cur, cur.pit(),cur.pos() - 1,
1003 return updateNeeded;
1006 if (cur.pit() > 0) {
1007 // Steps into the paragraph above
1008 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1014 bool Text::cursorRight(Cursor & cur)
1016 // Tell BufferView to test for FitCursor in any case!
1017 cur.updateFlags(Update::FitCursor);
1019 if (cur.pos() != cur.lastpos()) {
1021 return setCursor(cur, cur.pit(), cur.pos(),
1024 bool updateNeeded = false;
1025 // If checkAndActivateInset returns true, that means that
1026 // the cursor was placed inside it, so we're done
1027 if (!checkAndActivateInset(cur, true)) {
1028 if (cur.textRow().endpos() == cur.pos() + 1 &&
1029 cur.textRow().endpos() != cur.lastpos() &&
1030 !cur.paragraph().isLineSeparator(cur.pos()) &&
1031 !cur.paragraph().isNewline(cur.pos())) {
1034 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1036 return updateNeeded;
1039 if (cur.pit() != cur.lastpit())
1040 return setCursor(cur, cur.pit() + 1, 0);
1045 bool Text::cursorUp(Cursor & cur)
1047 // Tell BufferView to test for FitCursor in any case!
1048 cur.updateFlags(Update::FitCursor);
1050 TextMetrics const & tm = cur.bv().textMetrics(this);
1051 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1054 if (cur.pos() && cur.boundary())
1055 row = pm.pos2row(cur.pos()-1);
1057 row = pm.pos2row(cur.pos());
1059 int x = cur.targetX();
1061 // We want to keep the x-target on subsequent up movements
1062 // that cross beyond the end of short lines. Thus a special
1063 // handling when the cursor is at the end of line: Use the new
1064 // x-target only if the old one was before the end of line.
1065 if (cur.pos() != pm.rows()[row].endpos()
1066 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1067 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1072 if (!cur.selection()) {
1073 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1075 // Go to middle of previous row. 16 found to work OK;
1076 // 12 = top/bottom margin of display math
1077 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1078 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1079 cur.clearSelection();
1081 // This happens when you move out of an inset.
1082 // And to give the DEPM the possibility of doing
1083 // something we must provide it with two different
1089 cur.bv().checkDepm(dummy, old);
1093 bool updateNeeded = false;
1096 updateNeeded |= setCursor(cur, cur.pit(),
1097 tm.x2pos(cur.pit(), row - 1, x));
1098 } else if (cur.pit() > 0) {
1100 //cannot use 'par' now
1101 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1102 updateNeeded |= setCursor(cur, cur.pit(),
1103 tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x));
1108 return updateNeeded;
1112 bool Text::cursorDown(Cursor & cur)
1114 // Tell BufferView to test for FitCursor in any case!
1115 cur.updateFlags(Update::FitCursor);
1117 TextMetrics const & tm = cur.bv().textMetrics(this);
1118 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1121 if (cur.pos() && cur.boundary())
1122 row = pm.pos2row(cur.pos()-1);
1124 row = pm.pos2row(cur.pos());
1126 int x = cur.targetX();
1128 // We want to keep the x-target on subsequent down movements
1129 // that cross beyond the end of short lines. Thus a special
1130 // handling when the cursor is at the end of line: Use the new
1131 // x-target only if the old one was before the end of line.
1132 if (cur.pos() != pm.rows()[row].endpos()
1133 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1134 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1139 if (!cur.selection()) {
1140 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1142 // To middle of next row
1143 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1144 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1145 cur.clearSelection();
1147 // This happens when you move out of an inset.
1148 // And to give the DEPM the possibility of doing
1149 // something we must provide it with two different
1155 bool const changed = cur.bv().checkDepm(dummy, old);
1157 // Make sure that cur gets back whatever happened to dummy(Lgb)
1164 bool updateNeeded = false;
1166 if (row + 1 < int(pm.rows().size())) {
1167 updateNeeded |= setCursor(cur, cur.pit(),
1168 tm.x2pos(cur.pit(), row + 1, x));
1169 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1171 updateNeeded |= setCursor(cur, cur.pit(),
1172 tm.x2pos(cur.pit(), 0, x));
1177 return updateNeeded;
1181 bool Text::cursorUpParagraph(Cursor & cur)
1183 bool updated = false;
1185 updated = setCursor(cur, cur.pit(), 0);
1186 else if (cur.pit() != 0)
1187 updated = setCursor(cur, cur.pit() - 1, 0);
1192 bool Text::cursorDownParagraph(Cursor & cur)
1194 bool updated = false;
1195 if (cur.pit() != cur.lastpit())
1196 updated = setCursor(cur, cur.pit() + 1, 0);
1198 updated = setCursor(cur, cur.pit(), cur.lastpos());
1203 // fix the cursor `cur' after a characters has been deleted at `where'
1204 // position. Called by deleteEmptyParagraphMechanism
1205 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1207 // Do nothing if cursor is not in the paragraph where the
1208 // deletion occured,
1209 if (cur.pit() != where.pit())
1212 // If cursor position is after the deletion place update it
1213 if (cur.pos() > where.pos())
1216 // Check also if we don't want to set the cursor on a spot behind the
1217 // pagragraph because we erased the last character.
1218 if (cur.pos() > cur.lastpos())
1219 cur.pos() = cur.lastpos();
1223 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1224 Cursor & old, bool & need_anchor_change)
1226 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1228 Paragraph & oldpar = old.paragraph();
1230 // We allow all kinds of "mumbo-jumbo" when freespacing.
1231 if (oldpar.isFreeSpacing())
1234 /* Ok I'll put some comments here about what is missing.
1235 There are still some small problems that can lead to
1236 double spaces stored in the document file or space at
1237 the beginning of paragraphs(). This happens if you have
1238 the cursor between to spaces and then save. Or if you
1239 cut and paste and the selection have a space at the
1240 beginning and then save right after the paste. (Lgb)
1243 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1244 // delete the LineSeparator.
1247 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1248 // delete the LineSeparator.
1251 bool const same_inset = &old.inset() == &cur.inset();
1252 bool const same_par = same_inset && old.pit() == cur.pit();
1253 bool const same_par_pos = same_par && old.pos() == cur.pos();
1255 // If the chars around the old cursor were spaces, delete one of them.
1256 if (!same_par_pos) {
1257 // Only if the cursor has really moved.
1259 && old.pos() < oldpar.size()
1260 && oldpar.isLineSeparator(old.pos())
1261 && oldpar.isLineSeparator(old.pos() - 1)
1262 && !oldpar.isDeleted(old.pos() - 1)) {
1263 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1264 #ifdef WITH_WARNINGS
1265 #warning This will not work anymore when we have multiple views of the same buffer
1266 // In this case, we will have to correct also the cursors held by
1267 // other bufferviews. It will probably be easier to do that in a more
1268 // automated way in CursorSlice code. (JMarc 26/09/2001)
1270 // correct all cursor parts
1272 fixCursorAfterDelete(cur.top(), old.top());
1273 need_anchor_change = true;
1279 // only do our magic if we changed paragraph
1283 // don't delete anything if this is the ONLY paragraph!
1284 if (old.lastpit() == 0)
1287 // Do not delete empty paragraphs with keepempty set.
1288 if (oldpar.allowEmpty())
1291 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1293 recordUndo(old, Undo::ATOMIC,
1294 max(old.pit() - 1, pit_type(0)),
1295 min(old.pit() + 1, old.lastpit()));
1296 ParagraphList & plist = old.text()->paragraphs();
1297 plist.erase(boost::next(plist.begin(), old.pit()));
1299 // see #warning above
1300 if (cur.depth() >= old.depth()) {
1301 CursorSlice & curslice = cur[old.depth() - 1];
1302 if (&curslice.inset() == &old.inset()
1303 && curslice.pit() > old.pit()) {
1305 // since a paragraph has been deleted, all the
1306 // insets after `old' have been copied and
1307 // their address has changed. Therefore we
1308 // need to `regenerate' cur. (JMarc)
1309 cur.updateInsets(&(cur.bottom().inset()));
1310 need_anchor_change = true;
1316 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1317 need_anchor_change = true;
1318 // We return true here because the Paragraph contents changed and
1319 // we need a redraw before further action is processed.
1327 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1329 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1331 for (pit_type pit = first; pit <= last; ++pit) {
1332 Paragraph & par = pars_[pit];
1334 // We allow all kinds of "mumbo-jumbo" when freespacing.
1335 if (par.isFreeSpacing())
1338 for (pos_type pos = 1; pos < par.size(); ++pos) {
1339 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1340 && !par.isDeleted(pos - 1)) {
1341 if (par.eraseChar(pos - 1, trackChanges)) {
1347 // don't delete anything if this is the only remaining paragraph within the given range
1348 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1352 // don't delete empty paragraphs with keepempty set
1353 if (par.allowEmpty())
1356 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1357 pars_.erase(boost::next(pars_.begin(), pit));
1363 par.stripLeadingSpaces(trackChanges);
1368 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1370 recordUndo(cur, Undo::ATOMIC, first, last);
1374 void Text::recUndo(Cursor & cur, pit_type par) const
1376 recordUndo(cur, Undo::ATOMIC, par, par);